1
0
Fork 0
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:
michelc 2011-10-28 14:15:59 +02:00
commit cf27ad4196
22 changed files with 876 additions and 123 deletions

View file

@ -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
View file

@ -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
View file

@ -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

View file

@ -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/]

View file

@ -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

View file

@ -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/]

View file

@ -376,6 +376,7 @@ textileからメソッドを呼び出すことも、localsに変数を渡すこ
RDocテンプレートを使うにはRDocライブラリが必要です:
# rdoc/markup/to_htmlを読み込みます
require "rdoc"
require "rdoc/markup/to_html"
get '/' do

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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')} &&

View file

@ -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

View file

@ -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'

View file

@ -1,3 +1,3 @@
module Sinatra
VERSION = '1.3.0'
VERSION = '1.3.1'
end

View file

@ -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

View file

@ -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' }

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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