mirror of
https://github.com/sinatra/sinatra
synced 2023-03-27 23:18:01 -04:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
cf27ad4196
22 changed files with 876 additions and 123 deletions
|
@ -7,9 +7,9 @@ rvm:
|
|||
- jruby
|
||||
- ruby-head
|
||||
env:
|
||||
- "rack=1.3.0"
|
||||
- "rack=1.3.4"
|
||||
- "rack=master"
|
||||
- "tilt=1.3.2"
|
||||
- "tilt=1.3.3"
|
||||
- "tilt=master"
|
||||
notifications:
|
||||
recipients:
|
||||
|
|
37
CHANGES
37
CHANGES
|
@ -1,4 +1,23 @@
|
|||
= 1.3.0 / Not Yet Released
|
||||
= 1.3.2 / Not Yet Released
|
||||
|
||||
* Don't automatically add `Rack::CommonLogger` if `Rack::Server` is adding it,
|
||||
too. (Konstantin Haase)
|
||||
|
||||
* Setting `logging` to `nil` will avoid setting up `Rack::NullLogger`.
|
||||
(Konstantin Haase)
|
||||
|
||||
* Fix bug where rendering a second template in the same request after the
|
||||
first one raised an exception skipped the default layout (Nathan Baum)
|
||||
|
||||
= 1.3.1 / 2011-10-05
|
||||
|
||||
* Support adding more than one callback to the stream object. (Konstantin
|
||||
Haase)
|
||||
|
||||
* Fix for infinite loop when streaming on 1.9.2 with Thin from a modular
|
||||
application (Konstantin Haase)
|
||||
|
||||
= 1.3.0 / 2011-09-30
|
||||
|
||||
* Added `stream` helper method for easily creating streaming APIs, Server
|
||||
Sent Events or even WebSockets. See README for more on that topic.
|
||||
|
@ -12,7 +31,7 @@
|
|||
* Added support for HTTP PATCH requests. (Konstantin Haase)
|
||||
|
||||
* Use rack-protection to defend against common opportunistic attacks.
|
||||
(Konstantin Haase)
|
||||
(Josh Lane, Jacob Burkhart, Konstantin Haase)
|
||||
|
||||
* Support for Creole templates, Creole is a standardized wiki markup,
|
||||
supported by many wiki implementations. (Konstanin Haase)
|
||||
|
@ -82,6 +101,11 @@
|
|||
* Conditional requests on `etag` helper now work properly for unsafe HTTP
|
||||
methods. (Matthew Schinckel, Konstantin Haase)
|
||||
|
||||
* The `last_modified` helper does not stop execution and change the status code
|
||||
if the status code is something different than 200. (Konstantin Haase)
|
||||
|
||||
* Added support for If-Unmodified-Since header. (Konstantin Haase)
|
||||
|
||||
* `Sinatra::Base.run!` now prints to stderr rather than stdout. (Andrew
|
||||
Armenia)
|
||||
|
||||
|
@ -120,7 +144,14 @@
|
|||
* Fix handling of broken query params when displaying exceptions. (Luke
|
||||
Jahnke)
|
||||
|
||||
= 1.2.7 (backports release) / Not Yet Released
|
||||
= 1.2.8 / Not Yet Released
|
||||
|
||||
Backported from 1.3.2:
|
||||
|
||||
* Fix bug where rendering a second template in the same request after the
|
||||
first one raised an exception skipped the default layout (Nathan Baum)
|
||||
|
||||
= 1.2.7 (backports release) / 2011-09-30
|
||||
|
||||
Custom changes:
|
||||
|
||||
|
|
40
Gemfile
40
Gemfile
|
@ -19,9 +19,14 @@ gem 'ci_reporter', :group => :ci
|
|||
github = "git://github.com/%s.git"
|
||||
repos = { 'tilt' => github % "rtomayko/tilt", 'rack' => github % "rack/rack" }
|
||||
%w[tilt rack].each do |lib|
|
||||
dep = (ENV[lib] || 'stable').sub "#{lib}-", ''
|
||||
dep = nil if dep == 'stable'
|
||||
dep = {:git => repos[lib], :branch => dep} if dep and dep !~ /(\d+\.)+\d+/
|
||||
dep = case ENV[lib] || 'stable'
|
||||
when 'stable'
|
||||
nil
|
||||
when /(\d+\.)+\d+/
|
||||
"~> " + ENV[lib].sub("#{lib}-", '')
|
||||
else
|
||||
{:git => repos[lib], :branch => dep}
|
||||
end
|
||||
gem lib, dep
|
||||
end
|
||||
|
||||
|
@ -29,17 +34,10 @@ gem 'haml', '>= 3.0'
|
|||
gem 'sass'
|
||||
gem 'builder'
|
||||
gem 'erubis'
|
||||
gem 'less', '~> 1.0'
|
||||
|
||||
if RUBY_ENGINE == "maglev"
|
||||
gem 'liquid', :git => "https://github.com/Shopify/liquid.git"
|
||||
else
|
||||
gem 'liquid'
|
||||
end
|
||||
|
||||
gem 'liquid'
|
||||
gem 'slim', '~> 1.0'
|
||||
gem 'temple', '!= 0.3.3'
|
||||
gem 'RedCloth' if RUBY_VERSION < "1.9.3" and not RUBY_ENGINE.start_with? 'ma'
|
||||
gem 'RedCloth' if RUBY_VERSION < "1.9.3" and not RUBY_ENGINE == "macruby"
|
||||
gem 'coffee-script', '>= 2.0'
|
||||
gem 'rdoc'
|
||||
gem 'kramdown'
|
||||
|
@ -49,10 +47,16 @@ gem 'creole'
|
|||
if RUBY_ENGINE == 'jruby'
|
||||
gem 'nokogiri', '!= 1.5.0'
|
||||
gem 'jruby-openssl'
|
||||
elsif RUBY_ENGINE != 'maglev'
|
||||
else
|
||||
gem 'nokogiri'
|
||||
end
|
||||
|
||||
if RUBY_ENGINE == "ruby"
|
||||
gem 'less', '~> 2.0'
|
||||
else
|
||||
gem 'less', '~> 1.0'
|
||||
end
|
||||
|
||||
unless RUBY_ENGINE == 'jruby' && JRUBY_VERSION < "1.6.1" && !ENV['TRAVIS']
|
||||
# C extensions
|
||||
gem 'rdiscount'
|
||||
|
@ -62,16 +66,10 @@ unless RUBY_ENGINE == 'jruby' && JRUBY_VERSION < "1.6.1" && !ENV['TRAVIS']
|
|||
#gem 'bluecloth'
|
||||
end
|
||||
|
||||
if RUBY_ENGINE == 'maglev'
|
||||
gem 'json', :git => "https://github.com/MagLev/json.git"
|
||||
platforms :ruby_18, :jruby do
|
||||
gem 'json'
|
||||
gem 'markaby'
|
||||
gem 'radius'
|
||||
else
|
||||
platforms :ruby_18, :jruby do
|
||||
gem 'json'
|
||||
gem 'markaby'
|
||||
gem 'radius'
|
||||
end
|
||||
end
|
||||
|
||||
platforms :mri_18 do
|
||||
|
|
|
@ -1953,9 +1953,9 @@ SemVer und SemVerTag.
|
|||
* {Mailing-Liste}[http://groups.google.com/group/sinatrarb]
|
||||
* {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] auf http://freenode.net
|
||||
* {Sinatra Book}[http://sinatra-book.gittr.com] Kochbuch Tutorial
|
||||
* {Sinatra Book Contrib}[http://sinatra-book-contrib.com/] Sinatra-Rezepte aus
|
||||
* {Sinatra Recipes}[http://recipes.sinatrarb.com/] Sinatra-Rezepte aus
|
||||
der Community
|
||||
* API Dokumentation für die {aktuelle Version}[http://rubydoc.info/gems/sinatra]
|
||||
oder für {HEAD}[http://rubydoc.info/github/sinatra/sinatra] auf
|
||||
http://rubydoc.info
|
||||
* {CI Server}[http://ci.rkh.im/view/Sinatra/]
|
||||
* {CI Server}[http://ci.rkh.im/view/Sinatra/]
|
||||
|
|
|
@ -916,11 +916,16 @@ para <tt>Sinatra::Application</tt>. Si heredaste de
|
|||
<tt>Sinatra::Base</tt>, probablemente quieras habilitarlo manualmente:
|
||||
|
||||
class MiApp < Sinatra::Base
|
||||
configure(:production, :development) do
|
||||
configure :production, :development do
|
||||
enable :logging
|
||||
end
|
||||
end
|
||||
|
||||
Para evitar que se inicialice cualquier middleware de logging, configurá
|
||||
+logging+ a +nil+. Tené en cuenta que, cuando hagas esto, +logger+ va a
|
||||
devolver +nil+. Un caso común es cuando querés usar tu propio logger. Sinatra
|
||||
va a usar lo que encuentre en <tt>env['rack.logger']</tt>.
|
||||
|
||||
=== Tipos Mime
|
||||
|
||||
Cuando usás <tt>send_file</tt> o archivos estáticos tal vez tengas tipos mime
|
||||
|
@ -1055,6 +1060,23 @@ Usá la configuración <tt>:static_cache_control</tt> para agregar el encabezado
|
|||
<tt>Cache-Control</tt> a archivos estáticos (ver la sección de configuración
|
||||
para más detalles).
|
||||
|
||||
De acuerdo con la RFC 2616 tu aplicación debería comportarse diferente si a las
|
||||
cabeceras If-Match o If-None-Match se le asigna el valor <tt>*</tt> cuando el
|
||||
recurso solicitado ya existe. Sinatra asume para peticiones seguras (como get)
|
||||
e idempotentes (como put) que el recurso existe, mientras que para el resto
|
||||
(como post), que no. Podes cambiar este comportamiento con la opción
|
||||
<tt>:new_resource</tt>:
|
||||
|
||||
get '/crear' do
|
||||
etag '', :new_resource => true
|
||||
Articulo.create
|
||||
erb :nuevo_articulo
|
||||
end
|
||||
|
||||
Si querés seguir usando una weak ETag, indicalo con la opción <tt>:kind</tt>:
|
||||
|
||||
etag '', :new_resource => true, :kind => :weak
|
||||
|
||||
=== Enviando Archivos
|
||||
|
||||
Para enviar archivos, podés usar el método <tt>send_file</tt>:
|
||||
|
@ -1283,7 +1305,7 @@ Podés acceder a estas opciones utilizando el método <tt>settings</tt>:
|
|||
...
|
||||
end
|
||||
|
||||
==== Configurando la Protección de Ataques
|
||||
=== Configurando la Protección de Ataques
|
||||
|
||||
Sinatra usa {Rack::Protection}[https://github.com/rkh/rack-protection#readme]
|
||||
para defender a tu aplicación de los ataques más comunes. Tenés que tener en
|
||||
|
@ -1987,7 +2009,7 @@ siguiendo las especificaciones SemVer y SemVerTag.
|
|||
* {Lista de Correo}[http://groups.google.com/group/sinatrarb/topics]
|
||||
* {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] en http://freenode.net
|
||||
* {Sinatra Book}[http://sinatra-book.gittr.com] Tutorial (en inglés).
|
||||
* {Sinatra Book Contrib}[http://sinatra-book-contrib.com/] Recetas contribuidas
|
||||
* {Sinatra Recipes}[http://recipes.sinatrarb.com/] Recetas contribuidas
|
||||
por la comunidad (en inglés).
|
||||
* Documentación de la API para la
|
||||
{última versión liberada}[http://rubydoc.info/gems/sinatra] o para la
|
||||
|
|
|
@ -1988,9 +1988,9 @@ SemVer que SemVerTag.
|
|||
* {IRC : #sinatra}[irc://chat.freenode.net/#sinatra] sur http://freenode.net
|
||||
* {IRC : #sinatra}[irc://chat.freenode.net/#sinatra] on http://freenode.net
|
||||
* {Sinatra Book}[http://sinatra-book.gittr.com] Tutoriels et recettes
|
||||
* {Sinatra Book Contrib}[http://sinatra-book-contrib.com/] Recettes contribuées
|
||||
* {Sinatra Recipes}[http://recipes.sinatrarb.com/] Recettes contribuées
|
||||
par la communauté
|
||||
* Documentation API de la {dernière version}[http://rubydoc.info/gems/sinatra]
|
||||
ou du {HEAD courant}[http://rubydoc.info/github/sinatra/sinatra] sur
|
||||
http://rubydoc.info
|
||||
* {CI server}[http://ci.rkh.im/view/Sinatra/]
|
||||
* {CI server}[http://ci.rkh.im/view/Sinatra/]
|
||||
|
|
|
@ -376,6 +376,7 @@ textileからメソッドを呼び出すことも、localsに変数を渡すこ
|
|||
RDocテンプレートを使うにはRDocライブラリが必要です:
|
||||
|
||||
# rdoc/markup/to_htmlを読み込みます
|
||||
require "rdoc"
|
||||
require "rdoc/markup/to_html"
|
||||
|
||||
get '/' do
|
||||
|
|
26
README.rdoc
26
README.rdoc
|
@ -885,11 +885,16 @@ default, so if you inherit from <tt>Sinatra::Base</tt>, you probably want to
|
|||
enable it yourself:
|
||||
|
||||
class MyApp < Sinatra::Base
|
||||
configure(:production, :development) do
|
||||
configure :production, :development do
|
||||
enable :logging
|
||||
end
|
||||
end
|
||||
|
||||
To avoid any logging middleware to be set up, set the +logging+ setting to
|
||||
+nil+. However, keep in mind that +logger+ will in that case return +nil+. A
|
||||
common use case is when you want to set your own logger. Sinatra will use
|
||||
whatever it will find in <tt>env['rack.logger']</tt>.
|
||||
|
||||
=== Mime Types
|
||||
|
||||
When using <tt>send_file</tt> or static files you may have mime types Sinatra
|
||||
|
@ -1018,6 +1023,23 @@ try {rack-cache}[http://rtomayko.github.com/rack-cache/]:
|
|||
Use the <tt>:static_cache_control</tt> setting (see below) to add
|
||||
<tt>Cache-Control</tt> header info to static files.
|
||||
|
||||
According to RFC 2616 your application should behave differently if the If-Match
|
||||
or If-None-Match header is set to <tt>*</tt> depending on whether the resource
|
||||
requested is already in existence. Sinatra assumes resources for safe (like get)
|
||||
and idempotent (like put) requests are already in existence, whereas other
|
||||
resources (for instance for post requests), are treated as new resources. You
|
||||
can change this behavior by passing in a <tt>:new_resource</tt> option:
|
||||
|
||||
get '/create' do
|
||||
etag '', :new_resource => true
|
||||
Article.create
|
||||
erb :new_article
|
||||
end
|
||||
|
||||
If you still want to use a weak ETag, pass in a <tt>:kind</tt> option:
|
||||
|
||||
etag '', :new_resource => true, :kind => :weak
|
||||
|
||||
=== Sending Files
|
||||
|
||||
For sending files, you can use the <tt>send_file</tt> helper method:
|
||||
|
@ -1925,7 +1947,7 @@ SemVerTag.
|
|||
* {Mailing List}[http://groups.google.com/group/sinatrarb/topics]
|
||||
* {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] on http://freenode.net
|
||||
* {Sinatra Book}[http://sinatra-book.gittr.com] Cookbook Tutorial
|
||||
* {Sinatra Book Contrib}[http://sinatra-book-contrib.com/] Community
|
||||
* {Sinatra Recipes}[http://recipes.sinatrarb.com/] Community
|
||||
contributed recipes
|
||||
* API documentation for the {latest release}[http://rubydoc.info/gems/sinatra]
|
||||
or the {current HEAD}[http://rubydoc.info/github/sinatra/sinatra] on
|
||||
|
|
|
@ -1779,7 +1779,7 @@ SemVerTag.
|
|||
* {Группы рассылки}[http://groups.google.com/group/sinatrarb/topics]
|
||||
* {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] на http://freenode.net
|
||||
* {Sinatra Book}[http://sinatra-book.gittr.com] учебник и сборник рецептов
|
||||
* {Sinatra Book Contrib}[http://sinatra-book-contrib.com/] сборник рецептов
|
||||
* {Sinatra Recipes}[http://recipes.sinatrarb.com/] сборник рецептов
|
||||
* API документация к {последнему релизу}[http://rubydoc.info/gems/sinatra]
|
||||
или {текущему HEAD}[http://rubydoc.info/github/sinatra/sinatra] на
|
||||
http://rubydoc.info
|
||||
|
|
|
@ -492,6 +492,7 @@ Rack body对象或者HTTP状态码:
|
|||
需要引入 <tt>RDoc</tt> gem/library 以渲染RDoc模板:
|
||||
|
||||
# 需要在你的应用中引入rdoc/markup/to_html
|
||||
require "rdoc"
|
||||
require "rdoc/markup/to_html"
|
||||
|
||||
get '/' do
|
||||
|
|
4
Rakefile
4
Rakefile
|
@ -164,6 +164,10 @@ if defined?(Gem)
|
|||
end
|
||||
|
||||
task 'release' => ['test', package('.gem')] do
|
||||
if File.read("CHANGES") =~ /= \d\.\d\.\d . not yet released$/i
|
||||
fail 'please update changes first'
|
||||
end
|
||||
|
||||
sh <<-SH
|
||||
gem install #{package('.gem')} --local &&
|
||||
gem push #{package('.gem')} &&
|
||||
|
|
|
@ -51,7 +51,7 @@ module Sinatra
|
|||
private
|
||||
|
||||
def accept_entry(entry)
|
||||
type, *options = entry.gsub(/\s/, '').split(';')
|
||||
type, *options = entry.delete(' ').split(';')
|
||||
quality = 0 # we sort smalles first
|
||||
options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
|
||||
[type, [quality, type.count('*'), 1 - options.size]]
|
||||
|
@ -247,11 +247,14 @@ module Sinatra
|
|||
def self.defer(*) yield end
|
||||
|
||||
def initialize(scheduler = self.class, keep_open = false, &back)
|
||||
@back, @scheduler, @callback, @keep_open = back.to_proc, scheduler, nil, keep_open
|
||||
@back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
|
||||
@callbacks, @closed = [], false
|
||||
end
|
||||
|
||||
def close
|
||||
@scheduler.schedule { @callback.call if @callback }
|
||||
return if @closed
|
||||
@closed = true
|
||||
@scheduler.schedule { @callbacks.each { |c| c.call }}
|
||||
end
|
||||
|
||||
def each(&front)
|
||||
|
@ -272,7 +275,7 @@ module Sinatra
|
|||
end
|
||||
|
||||
def callback(&block)
|
||||
@callback = block
|
||||
@callbacks << block
|
||||
end
|
||||
|
||||
alias errback callback
|
||||
|
@ -308,7 +311,7 @@ module Sinatra
|
|||
hash = {}
|
||||
end
|
||||
|
||||
values = values.map { |value| value.to_s.tr('_','-') }
|
||||
values.map! { |value| value.to_s.tr('_','-') }
|
||||
hash.each do |key, value|
|
||||
key = key.to_s.tr('_', '-')
|
||||
value = value.to_i if key == "max-age"
|
||||
|
@ -355,8 +358,19 @@ module Sinatra
|
|||
return unless time
|
||||
time = time_for time
|
||||
response['Last-Modified'] = time.httpdate
|
||||
# compare based on seconds since epoch
|
||||
halt 304 if Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i >= time.to_i
|
||||
return if env['HTTP_IF_NONE_MATCH']
|
||||
|
||||
if status == 200 and env['HTTP_IF_MODIFIED_SINCE']
|
||||
# compare based on seconds since epoch
|
||||
since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i
|
||||
halt 304 if since >= time.to_i
|
||||
end
|
||||
|
||||
if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE']
|
||||
# compare based on seconds since epoch
|
||||
since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i
|
||||
halt 412 if since < time.to_i
|
||||
end
|
||||
rescue ArgumentError
|
||||
end
|
||||
|
||||
|
@ -369,18 +383,27 @@ module Sinatra
|
|||
# When the current request includes an 'If-None-Match' header with a
|
||||
# matching etag, execution is immediately halted. If the request method is
|
||||
# GET or HEAD, a '304 Not Modified' response is sent.
|
||||
def etag(value, kind = :strong)
|
||||
raise ArgumentError, ":strong or :weak expected" unless [:strong,:weak].include?(kind)
|
||||
def etag(value, options = {})
|
||||
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
||||
options = {:kind => options} unless Hash === options
|
||||
kind = options[:kind] || :strong
|
||||
new_resource = options.fetch(:new_resource) { request.post? }
|
||||
|
||||
unless [:strong, :weak].include?(kind)
|
||||
raise ArgumentError, ":strong or :weak expected"
|
||||
end
|
||||
|
||||
value = '"%s"' % value
|
||||
value = 'W/' + value if kind == :weak
|
||||
response['ETag'] = value
|
||||
|
||||
if etags = env['HTTP_IF_NONE_MATCH']
|
||||
etags = etags.split(/\s*,\s*/)
|
||||
if etags.include?(value) or etags.include?('*')
|
||||
halt 304 if request.safe?
|
||||
else
|
||||
halt 412 unless request.safe?
|
||||
if success? or status == 304
|
||||
if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
|
||||
halt(request.safe? ? 304 : 412)
|
||||
end
|
||||
|
||||
if env['HTTP_IF_MATCH']
|
||||
halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -445,6 +468,14 @@ module Sinatra
|
|||
rescue Exception
|
||||
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Helper method checking if a ETag value list includes the current ETag.
|
||||
def etag_matches?(list, new_resource = request.post?)
|
||||
return !new_resource if list == '*'
|
||||
list.to_s.split(/\s*,\s*/).include? response['ETag']
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -588,11 +619,14 @@ module Sinatra
|
|||
scope = options.delete(:scope) || self
|
||||
|
||||
# compile and render template
|
||||
layout_was = @default_layout
|
||||
@default_layout = false
|
||||
template = compile_template(engine, data, options, views)
|
||||
output = template.render(scope, locals, &block)
|
||||
@default_layout = layout_was
|
||||
begin
|
||||
layout_was = @default_layout
|
||||
@default_layout = false
|
||||
template = compile_template(engine, data, options, views)
|
||||
output = template.render(scope, locals, &block)
|
||||
ensure
|
||||
@default_layout = layout_was
|
||||
end
|
||||
|
||||
# render layout
|
||||
if layout
|
||||
|
@ -817,7 +851,7 @@ module Sinatra
|
|||
return unless path.start_with?(public_dir) and File.file?(path)
|
||||
|
||||
env['sinatra.static_file'] = path
|
||||
cache_control *settings.static_cache_control if settings.static_cache_control?
|
||||
cache_control(*settings.static_cache_control) if settings.static_cache_control?
|
||||
send_file path, :disposition => nil
|
||||
end
|
||||
|
||||
|
@ -1184,9 +1218,8 @@ module Sinatra
|
|||
def compile(path)
|
||||
keys = []
|
||||
if path.respond_to? :to_str
|
||||
special_chars = %w{. + ( ) $}
|
||||
pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) }
|
||||
pattern.gsub! /((:\w+)|\*)/ do |match|
|
||||
pattern.gsub!(/((:\w+)|\*)/) do |match|
|
||||
if match == "*"
|
||||
keys << 'splat'
|
||||
"(.*?)"
|
||||
|
@ -1320,21 +1353,34 @@ module Sinatra
|
|||
|
||||
def setup_logging(builder)
|
||||
if logging?
|
||||
builder.use Rack::CommonLogger
|
||||
if logging.respond_to? :to_int
|
||||
builder.use Rack::Logger, logging
|
||||
else
|
||||
builder.use Rack::Logger
|
||||
end
|
||||
setup_common_logger(builder)
|
||||
setup_custom_logger(builder)
|
||||
elsif logging == false
|
||||
setup_null_logger(builder)
|
||||
end
|
||||
end
|
||||
|
||||
def setup_null_logger(builder)
|
||||
builder.use Rack::NullLogger
|
||||
end
|
||||
|
||||
def setup_common_logger(builder)
|
||||
return if ["development", "deployment", nil].include? ENV["RACK_ENV"]
|
||||
builder.use Rack::CommonLogger
|
||||
end
|
||||
|
||||
def setup_custom_logger(builder)
|
||||
if logging.respond_to? :to_int
|
||||
builder.use Rack::Logger, logging
|
||||
else
|
||||
builder.use Rack::NullLogger
|
||||
builder.use Rack::Logger
|
||||
end
|
||||
end
|
||||
|
||||
def setup_protection(builder)
|
||||
return unless protection?
|
||||
options = Hash === protection ? protection.dup : {}
|
||||
options[:except] = Array options[:except]
|
||||
options[:except] = Array(options[:except] || :escaped_params)
|
||||
options[:except] += [:session_hijacking, :remote_token] unless sessions?
|
||||
builder.use Rack::Protection, options
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module Sinatra
|
|||
# on this path by default.
|
||||
set :app_file, caller_files.first || $0
|
||||
|
||||
set :run, Proc.new { $0 == app_file }
|
||||
set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) }
|
||||
|
||||
if run? && ARGV.any?
|
||||
require 'optparse'
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module Sinatra
|
||||
VERSION = '1.3.0'
|
||||
VERSION = '1.3.1'
|
||||
end
|
||||
|
|
|
@ -7,12 +7,12 @@ Gem::Specification.new 'sinatra', Sinatra::VERSION do |s|
|
|||
s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet", "Konstantin Haase"]
|
||||
s.email = "sinatrarb@googlegroups.com"
|
||||
s.homepage = "http://www.sinatrarb.com/"
|
||||
s.files = `git ls-files`.split("\n")
|
||||
s.files = `git ls-files`.split("\n") - %w[.gitignore .travis.yml]
|
||||
s.test_files = s.files.select { |p| p =~ /^test\/.*_test.rb/ }
|
||||
s.extra_rdoc_files = s.files.select { |p| p =~ /^README/ } << 'LICENSE'
|
||||
s.rdoc_options = %w[--line-numbers --inline-source --title Sinatra --main README.rdoc]
|
||||
|
||||
s.add_dependency 'rack', '~> 1.3'
|
||||
s.add_dependency 'rack-protection', '~> 1.1'
|
||||
s.add_dependency 'tilt', '~> 1.3'
|
||||
s.add_dependency 'rack', '~> 1.3', '>= 1.3.4'
|
||||
s.add_dependency 'rack-protection', '~> 1.1', '>= 1.1.2'
|
||||
s.add_dependency 'tilt', '~> 1.3', '>= 1.3.3'
|
||||
end
|
||||
|
|
|
@ -97,6 +97,17 @@ class BeforeFilterTest < Test::Unit::TestCase
|
|||
assert_equal 'cool', body
|
||||
end
|
||||
|
||||
it "properly unescapes parameters" do
|
||||
mock_app {
|
||||
before { @foo = params['foo'] }
|
||||
get('/foo') { @foo }
|
||||
}
|
||||
|
||||
get '/foo?foo=bar%3Abaz%2Fbend'
|
||||
assert ok?
|
||||
assert_equal 'bar:baz/bend', body
|
||||
end
|
||||
|
||||
it "runs filters defined in superclasses" do
|
||||
base = Class.new(Sinatra::Base)
|
||||
base.before { @foo = 'hello from superclass' }
|
||||
|
|
|
@ -69,7 +69,15 @@ class Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def assert_body(value)
|
||||
assert_equal value.lstrip.gsub(/\s*\n\s*/, ""), body.lstrip.gsub(/\s*\n\s*/, "")
|
||||
if value.respond_to? :to_str
|
||||
assert_equal value.lstrip.gsub(/\s*\n\s*/, ""), body.lstrip.gsub(/\s*\n\s*/, "")
|
||||
else
|
||||
assert_match value, body
|
||||
end
|
||||
end
|
||||
|
||||
def assert_status(expected)
|
||||
assert_equal Integer(expected), Integer(status)
|
||||
end
|
||||
|
||||
def assert_like(a,b)
|
||||
|
|
|
@ -858,6 +858,20 @@ class HelpersTest < Test::Unit::TestCase
|
|||
assert ! response['Last-Modified']
|
||||
end
|
||||
|
||||
it 'does not change a status other than 200' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
status 299
|
||||
last_modified Time.at(0)
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT')
|
||||
assert_status 299
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
[Time.now, DateTime.now, Date.today, Time.now.to_i,
|
||||
Struct.new(:to_time).new(Time.now) ].each do |last_modified_time|
|
||||
describe "with #{last_modified_time.class.name}" do
|
||||
|
@ -955,74 +969,641 @@ class HelpersTest < Test::Unit::TestCase
|
|||
assert_equal '', body
|
||||
end
|
||||
end
|
||||
|
||||
context "If-Unmodified-Since" do
|
||||
it 'results in 200 if resource has not been modified' do
|
||||
get '/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' }
|
||||
assert_equal 200, status
|
||||
assert_equal 'Boo!', body
|
||||
end
|
||||
|
||||
it 'results in 412 if resource has been modified' do
|
||||
get '/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => Time.at(0).httpdate }
|
||||
assert_equal 412, status
|
||||
assert_equal '', body
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'etag' do
|
||||
setup do
|
||||
mock_app {
|
||||
get '/' do
|
||||
body { 'Hello World' }
|
||||
etag 'FOO'
|
||||
'Boo!'
|
||||
context "safe requests" do
|
||||
it 'returns 200 for normal requests' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post '/' do
|
||||
etag 'FOO'
|
||||
'Matches!'
|
||||
get('/')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
context "If-None-Match" do
|
||||
it 'returns 304 when If-None-Match is *' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
||||
assert_status 304
|
||||
assert_body ''
|
||||
end
|
||||
}
|
||||
|
||||
it 'returns 200 when If-None-Match is * for new resources' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo', :new_resource => true
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 304 when If-None-Match is * for existing resources' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo', :new_resource => false
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
||||
assert_status 304
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 304 when If-None-Match is the etag' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
||||
assert_status 304
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 304 when If-None-Match includes the etag' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
|
||||
assert_status 304
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 200 when If-None-Match does not include the etag' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'ignores If-Modified-Since if If-None-Match does not match' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo'
|
||||
last_modified Time.at(0)
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'does not change a status code other than 2xx or 304' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
status 499
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
||||
assert_status 499
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'does change 2xx status codes' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
status 299
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
||||
assert_status 304
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'does not send a body on 304 status codes' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
status 304
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
||||
assert_status 304
|
||||
assert_body ''
|
||||
end
|
||||
end
|
||||
|
||||
context "If-Match" do
|
||||
it 'returns 200 when If-Match is the etag' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_MATCH' => '"foo"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 200 when If-Match includes the etag' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 200 when If-Match is *' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_MATCH' => '*')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 412 when If-Match is * for new resources' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo', :new_resource => true
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_MATCH' => '*')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 200 when If-Match is * for existing resources' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo', :new_resource => false
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_MATCH' => '*')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 412 when If-Match does not include the etag' do
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
get('/', {}, 'HTTP_IF_MATCH' => '"bar"')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets the ETag header' do
|
||||
get '/'
|
||||
assert_equal '"FOO"', response['ETag']
|
||||
context "idempotent requests" do
|
||||
it 'returns 200 for normal requests' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
context "If-None-Match" do
|
||||
it 'returns 412 when If-None-Match is *' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 200 when If-None-Match is * for new resources' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo', :new_resource => true
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 412 when If-None-Match is * for existing resources' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo', :new_resource => false
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 412 when If-None-Match is the etag' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 412 when If-None-Match includes the etag' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 200 when If-None-Match does not include the etag' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'ignores If-Modified-Since if If-None-Match does not match' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo'
|
||||
last_modified Time.at(0)
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
end
|
||||
|
||||
context "If-Match" do
|
||||
it 'returns 200 when If-Match is the etag' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_MATCH' => '"foo"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 200 when If-Match includes the etag' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 200 when If-Match is *' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_MATCH' => '*')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 412 when If-Match is * for new resources' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo', :new_resource => true
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_MATCH' => '*')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 200 when If-Match is * for existing resources' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo', :new_resource => false
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_MATCH' => '*')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 412 when If-Match does not include the etag' do
|
||||
mock_app do
|
||||
put '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
put('/', {}, 'HTTP_IF_MATCH' => '"bar"')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns a body when conditional get misses' do
|
||||
get '/'
|
||||
assert_equal 200, status
|
||||
assert_equal 'Boo!', body
|
||||
end
|
||||
context "post requests" do
|
||||
it 'returns 200 for normal requests' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns a body when posting with no If-None-Match header' do
|
||||
post '/'
|
||||
assert_equal 200, status
|
||||
assert_equal 'Matches!', body
|
||||
end
|
||||
post('/')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns a body when conditional post matches' do
|
||||
post '/', {}, { 'HTTP_IF_NONE_MATCH' => '"FOO"' }
|
||||
assert_equal 200, status
|
||||
assert_equal 'Matches!', body
|
||||
end
|
||||
context "If-None-Match" do
|
||||
it 'returns 200 when If-None-Match is *' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
it 'halts with 412 when conditional post misses' do
|
||||
post '/', {}, { 'HTTP_IF_NONE_MATCH' => '"BAR"' }
|
||||
assert_equal 412, status
|
||||
assert_equal '', body
|
||||
end
|
||||
post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'halts when a conditional GET matches' do
|
||||
get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"FOO"' }
|
||||
assert_equal 304, status
|
||||
assert_equal '', body
|
||||
end
|
||||
it 'returns 200 when If-None-Match is * for new resources' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo', :new_resource => true
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
it 'should handle multiple ETag values in If-None-Match header' do
|
||||
get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"BAR", *' }
|
||||
assert_equal 304, status
|
||||
assert_equal '', body
|
||||
post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 412 when If-None-Match is * for existing resources' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo', :new_resource => false
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 412 when If-None-Match is the etag' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 412 when If-None-Match includes the etag' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 200 when If-None-Match does not include the etag' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'ignores If-Modified-Since if If-None-Match does not match' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo'
|
||||
last_modified Time.at(0)
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
end
|
||||
|
||||
context "If-Match" do
|
||||
it 'returns 200 when If-Match is the etag' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post('/', {}, 'HTTP_IF_MATCH' => '"foo"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 200 when If-Match includes the etag' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 412 when If-Match is *' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post('/', {}, 'HTTP_IF_MATCH' => '*')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 412 when If-Match is * for new resources' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo', :new_resource => true
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post('/', {}, 'HTTP_IF_MATCH' => '*')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
|
||||
it 'returns 200 when If-Match is * for existing resources' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo', :new_resource => false
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post('/', {}, 'HTTP_IF_MATCH' => '*')
|
||||
assert_status 200
|
||||
assert_body 'ok'
|
||||
end
|
||||
|
||||
it 'returns 412 when If-Match does not include the etag' do
|
||||
mock_app do
|
||||
post '/' do
|
||||
etag 'foo'
|
||||
'ok'
|
||||
end
|
||||
end
|
||||
|
||||
post('/', {}, 'HTTP_IF_MATCH' => '"bar"')
|
||||
assert_status 412
|
||||
assert_body ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses a weak etag with the :weak option' do
|
||||
mock_app {
|
||||
mock_app do
|
||||
get '/' do
|
||||
etag 'FOO', :weak
|
||||
"that's weak, dude."
|
||||
end
|
||||
}
|
||||
end
|
||||
get '/'
|
||||
assert_equal 'W/"FOO"', response['ETag']
|
||||
end
|
||||
|
@ -1130,6 +1711,16 @@ class HelpersTest < Test::Unit::TestCase
|
|||
assert !io.string.include?("INFO -- : Program started")
|
||||
assert !io.string.include?("WARN -- : Nothing to do")
|
||||
end
|
||||
|
||||
it 'does not create a logger when logging is set to nil' do
|
||||
mock_app do
|
||||
set :logging, nil
|
||||
get('/') { logger.inspect }
|
||||
end
|
||||
|
||||
get '/'
|
||||
assert_body 'nil'
|
||||
end
|
||||
end
|
||||
|
||||
module ::HelperOne; def one; '1'; end; end
|
||||
|
|
|
@ -16,7 +16,7 @@ class LessTest < Test::Unit::TestCase
|
|||
it 'renders inline Less strings' do
|
||||
less_app { less "@white_color: #fff; #main { background-color: @white_color }" }
|
||||
assert ok?
|
||||
assert_equal "#main { background-color: #ffffff; }\n", body
|
||||
assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "")
|
||||
end
|
||||
|
||||
it 'defaults content type to css' do
|
||||
|
@ -45,13 +45,13 @@ class LessTest < Test::Unit::TestCase
|
|||
it 'renders .less files in views path' do
|
||||
less_app { less :hello }
|
||||
assert ok?
|
||||
assert_equal "#main { background-color: #ffffff; }\n", body
|
||||
assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "")
|
||||
end
|
||||
|
||||
it 'ignores the layout option' do
|
||||
less_app { less :hello, :layout => :layout2 }
|
||||
assert ok?
|
||||
assert_equal "#main { background-color: #ffffff; }\n", body
|
||||
assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "")
|
||||
end
|
||||
|
||||
it "raises error if template not found" do
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require File.expand_path('../helper', __FILE__)
|
||||
|
||||
begin
|
||||
require 'rdoc'
|
||||
require 'rdoc/markup/to_html'
|
||||
|
||||
class RdocTest < Test::Unit::TestCase
|
||||
|
@ -15,13 +16,13 @@ class RdocTest < Test::Unit::TestCase
|
|||
it 'renders inline rdoc strings' do
|
||||
rdoc_app { rdoc '= Hiya' }
|
||||
assert ok?
|
||||
assert_body "<h1>Hiya</h1>"
|
||||
assert_body /<h1[^>]*>Hiya<\/h1>/
|
||||
end
|
||||
|
||||
it 'renders .rdoc files in views path' do
|
||||
rdoc_app { rdoc :hello }
|
||||
assert ok?
|
||||
assert_body "<h1>Hello From RDoc</h1>"
|
||||
assert_body /<h1[^>]*>Hello From RDoc<\/h1>/
|
||||
end
|
||||
|
||||
it "raises error if template not found" do
|
||||
|
|
|
@ -114,6 +114,8 @@ class RoutingTest < Test::Unit::TestCase
|
|||
|
||||
it 'matches empty PATH_INFO to "" if a route is defined for ""' do
|
||||
mock_app do
|
||||
disable :protection
|
||||
|
||||
get '/' do
|
||||
'did not work'
|
||||
end
|
||||
|
|
|
@ -56,6 +56,16 @@ class StreamingTest < Test::Unit::TestCase
|
|||
assert_equal 0, final
|
||||
end
|
||||
|
||||
it 'allows adding more than one callback' do
|
||||
a = b = false
|
||||
stream = Stream.new { }
|
||||
stream.callback { a = true }
|
||||
stream.callback { b = true }
|
||||
stream.each { |str| }
|
||||
assert a, 'should trigger first callback'
|
||||
assert b, 'should trigger second callback'
|
||||
end
|
||||
|
||||
class MockScheduler
|
||||
def initialize(*) @schedule, @defer = [], [] end
|
||||
def schedule(&block) @schedule << block end
|
||||
|
@ -97,4 +107,9 @@ class StreamingTest < Test::Unit::TestCase
|
|||
scheduler.defer!
|
||||
assert_raise(RuntimeError) { scheduler.schedule! }
|
||||
end
|
||||
|
||||
it 'does not trigger an infinite loop if you call close in a callback' do
|
||||
stream = Stream.new { |out| out.callback { out.close }}
|
||||
stream.each { |str| }
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue