optionsful2 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +15 -0
- data/README.textile +91 -0
- data/Rakefile +11 -0
- data/lib/baurets/optionsful/config.rb +49 -0
- data/lib/baurets/optionsful/introspections.rb +72 -0
- data/lib/baurets/optionsful/server.rb +52 -0
- data/lib/baurets/optionsful/version.rb +16 -0
- data/lib/optionsful.rb +10 -0
- data/lib/tasks/optionsful.rake +8 -0
- data/rails/init.rb +9 -0
- data/samples/optionsful.yml +14 -0
- data/spec/fixtures/optionsful.yml +11 -0
- data/spec/fixtures/optionsful_bug.yml +0 -0
- data/spec/optionsful_config_spec.rb +40 -0
- data/spec/optionsful_server_spec.rb +335 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +72 -0
- metadata +83 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Copyright (c) 2010 Marco Antonio Gonzalez Junior (kayaman@baurets.net).
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
4
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
5
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
6
|
+
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
7
|
+
|
8
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions
|
9
|
+
of the Software.
|
10
|
+
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
12
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
13
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
14
|
+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
15
|
+
DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
h1. *Optionsful*
|
2
|
+
|
3
|
+
Provide HTTP OPTIONS support for the Ruby on Rails framework.
|
4
|
+
|
5
|
+
* Note for the impatient: installation instructions below.
|
6
|
+
* Note for the dummies: change host names and paths properly.
|
7
|
+
* Note for the unfaithful: run @rake routes@ to validate the results.
|
8
|
+
|
9
|
+
h2. "-No! No! No! *-Show me the code!*"
|
10
|
+
|
11
|
+
h3. Retrieving an HTTP OPTIONS request via telnet:
|
12
|
+
|
13
|
+
<pre>
|
14
|
+
|
15
|
+
$ telnet localhost 3000
|
16
|
+
OPTIONS /posts HTTP/1.1
|
17
|
+
Host: http://localhost:3000
|
18
|
+
|
19
|
+
HTTP/1.1 204 No Content
|
20
|
+
Allow: GET, POST
|
21
|
+
Connection: close
|
22
|
+
Date: Thu, 22 Jul 2010 17:20:27 GMT
|
23
|
+
Link: "<http://localhost:3000/optionsful/posts>; type=text/html; rel=help"
|
24
|
+
|
25
|
+
OPTIONS /posts/1 HTTP/1.1
|
26
|
+
Host: http://localhost:3000
|
27
|
+
|
28
|
+
HTTP/1.1 204 No Content
|
29
|
+
Allow: GET, PUT, DELETE
|
30
|
+
Connection: close
|
31
|
+
Date: Thu, 22 Jul 2010 18:14:24 GMT
|
32
|
+
Link: "<http://localhost:3000/optionsful/posts/1/>; type=text/html; rel=help"
|
33
|
+
|
34
|
+
OPTIONS /posts/1/comments HTTP/1.1
|
35
|
+
Host: http://localhost:3000
|
36
|
+
|
37
|
+
HTTP/1.1 204 No Content
|
38
|
+
Allow: GET, POST
|
39
|
+
Connection: close
|
40
|
+
Date: Thu, 22 Jul 2010 18:12:43 GMT
|
41
|
+
Link: "<http://localhost:3000/optionsful/posts/1/comments>; type=text/html; rel=help"
|
42
|
+
|
43
|
+
</pre>
|
44
|
+
~Note the empty line which is part of the HTTP protocol.~
|
45
|
+
|
46
|
+
h3. Telnet is the geek way. You would like better to use an HTTP client software. I use "HTTP Client":http://ditchnet.org/httpclient/ on Mac OS X.
|
47
|
+
|
48
|
+
h2. INSTALLATION:
|
49
|
+
|
50
|
+
# Change directory to your Ruby on Rails web application,
|
51
|
+
# Add gem dependency to @config/environment.rb@:
|
52
|
+
<pre>
|
53
|
+
config.gem "optionsful"
|
54
|
+
</pre>
|
55
|
+
# To install it, run @rake gems:install@ or:
|
56
|
+
<pre>
|
57
|
+
$ gem install optionsful
|
58
|
+
</pre>
|
59
|
+
# Enjoy! And give feedback! :)
|
60
|
+
|
61
|
+
h2. Link header WARNING
|
62
|
+
|
63
|
+
To enable the Link URI on your application, check the "Wisdomful":http://github.com/kayaman/wisdomful project!
|
64
|
+
|
65
|
+
h3. Link base path (Note: _Work in Progress_)
|
66
|
+
|
67
|
+
* To change the generated URI base path, install and edit the configuration file:
|
68
|
+
** run @rake optionsful:yml@
|
69
|
+
** edit the 'optionsful.yml' file at your application's 'config' folder.
|
70
|
+
* Example:
|
71
|
+
<pre>
|
72
|
+
development:
|
73
|
+
http:
|
74
|
+
base_path: /blopts
|
75
|
+
</pre>
|
76
|
+
|
77
|
+
* Note: @Allow@ and @Link@ are expected headers on a response to an HTTP OPTIONS request. I will think about some features to add more flexibility on the @Link@ generation pretty soon.
|
78
|
+
|
79
|
+
h2. KNOWN ISSUES
|
80
|
+
* Rails route recognition still need some work
|
81
|
+
* Platform: ruby 1.8.7, rails 2.3.8, rack 1.2.1
|
82
|
+
|
83
|
+
h2. Get involved
|
84
|
+
* Mailing list: http://groups.google.com/group/optionsful
|
85
|
+
* Bug tracker : http://kayaman.lighthouseapp.com/projects/56438-optionsful/overview
|
86
|
+
|
87
|
+
h2. CONTRIBUTORS
|
88
|
+
* Me, myself and I, so far.
|
89
|
+
* You are welcome, do it. ;-)
|
90
|
+
|
91
|
+
Copyright (c) 2010 Marco Antonio Gonzalez Junior, kayaman@baurets.net, released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'metric_fu'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
6
|
+
t.spec_files = Dir.glob( File.dirname(__FILE__) + '/spec/**/*_spec.rb' )
|
7
|
+
t.spec_opts << '--color --format specdoc'
|
8
|
+
t.rcov = true
|
9
|
+
end
|
10
|
+
|
11
|
+
task :default => :spec
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Baurets
|
2
|
+
module Optionsful
|
3
|
+
class Config
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@config = configure_options(options)
|
7
|
+
setup
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def base_path
|
12
|
+
@config[:http][:base_path]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def configure_options(options = {})
|
18
|
+
default_opts = { :http => { :base_path => "/optionsful"}, :file => "", :environment => "development" }
|
19
|
+
conf = {}
|
20
|
+
if defined? RAILS_ROOT
|
21
|
+
conf = default_opts.merge!({:file => (File.join(RAILS_ROOT, 'config', 'optionsful.yml'))})
|
22
|
+
else
|
23
|
+
conf = default_opts.merge!({ :http => { :base_path => "/optionsful"} })
|
24
|
+
end
|
25
|
+
conf = conf.merge!(options) unless options.empty?
|
26
|
+
conf
|
27
|
+
end
|
28
|
+
|
29
|
+
def setup
|
30
|
+
require "yaml"
|
31
|
+
yaml_file = @config[:file]
|
32
|
+
begin
|
33
|
+
if File.exist? yaml_file
|
34
|
+
conf = YAML::load_file(yaml_file)[@config[:environment]].symbolize_keys
|
35
|
+
configure(conf) if conf
|
36
|
+
end
|
37
|
+
rescue
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def configure(conf)
|
42
|
+
@config[:http][:base_path] = conf[:http]["base_path"] if (conf[:http] && conf[:http]["base_path"])
|
43
|
+
@config[:file] = conf[:file] if conf[:file]
|
44
|
+
@config[:environment] = conf[:environment] if conf[:environment]
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Baurets
|
2
|
+
module Optionsful
|
3
|
+
module Introspections
|
4
|
+
|
5
|
+
def self.is_part_static?(routes, index, value)
|
6
|
+
routes.each do |route|
|
7
|
+
return true if route[0][index] == value
|
8
|
+
end
|
9
|
+
return false
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def self.do_routing_introspection
|
15
|
+
returning Array.new do |routes|
|
16
|
+
route_requirements = nil
|
17
|
+
ActionController::Routing::Routes.named_routes.map.each do |named_route|
|
18
|
+
name = named_route[0].to_s
|
19
|
+
routes << [[name], ["GET", named_route[1].requirements[:action]], {:controller => named_route[1].requirements[:controller], :action => named_route[1].requirements[:action]}]
|
20
|
+
#TODO ANY ?!?
|
21
|
+
end
|
22
|
+
ActionController::Routing::Routes.routes.each do |route|
|
23
|
+
static_path = []
|
24
|
+
route.segments.each do |segment|
|
25
|
+
route_requirements = route.requirements #TODO validate
|
26
|
+
if segment.kind_of?(ActionController::Routing::StaticSegment)
|
27
|
+
static_path << segment.value if (segment.respond_to?(:value) && segment.value != "/")
|
28
|
+
elsif segment.kind_of?(ActionController::Routing::DynamicSegment)
|
29
|
+
static_path << :dynamic unless (segment.respond_to?(:key) && segment.key == :format)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
routes << [static_path, [route.conditions[:method].to_s.upcase, route_requirements[:action]], route_requirements] unless route.conditions[:method].nil?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.guess_route(routes, path)
|
38
|
+
guess = []
|
39
|
+
parts = prepare_request_path(path)
|
40
|
+
index = 0
|
41
|
+
parts.each do |part|
|
42
|
+
if is_part_static?(routes, index, part)
|
43
|
+
guess << part
|
44
|
+
else
|
45
|
+
guess << :dynamic
|
46
|
+
end
|
47
|
+
index += 1
|
48
|
+
end
|
49
|
+
guess
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.do_the_matches(routes, route_guess)
|
53
|
+
allow = ""
|
54
|
+
routes.each do |route|
|
55
|
+
if route.first == route_guess
|
56
|
+
allow += (route[1][0].to_s.upcase + "|") unless allow.include?(route[1][0].to_s.upcase)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
allow = allow.split("|").join(", ")
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.prepare_request_path(path)
|
63
|
+
path_parts = []
|
64
|
+
path = path[0..(path.rindex('.')-1)] if path.include?('.')
|
65
|
+
path_parts = path.split("/")
|
66
|
+
path_parts.delete("")
|
67
|
+
path_parts
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Baurets
|
2
|
+
module Optionsful
|
3
|
+
class Server
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
@config = ::Baurets::Optionsful::Config.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
unless env["REQUEST_METHOD"] == "OPTIONS"
|
12
|
+
@app.call(env)
|
13
|
+
else
|
14
|
+
extract_options_information(env)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def extract_options_information(env)
|
21
|
+
allows = extract_allowed_methods(env)
|
22
|
+
if allows.empty?
|
23
|
+
[404, {}, "Not found."]
|
24
|
+
else
|
25
|
+
[204, {"Allow" => allows, "Link" => "\"#{build_help_link(env)}\""}, ""]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_allowed_methods(env)
|
30
|
+
# do routing introspection:
|
31
|
+
routes = ::Baurets::Optionsful::Introspections.do_routing_introspection
|
32
|
+
# do request path investigation
|
33
|
+
path = env["PATH_INFO"]
|
34
|
+
route_guess = ::Baurets::Optionsful::Introspections.guess_route(routes, path)
|
35
|
+
|
36
|
+
puts "\nPATH: \n #{path}\n"
|
37
|
+
puts "\nROUTES: \n #{routes.inspect}\n"
|
38
|
+
puts "\nGUESS: \n #{route_guess.inspect}\n"
|
39
|
+
|
40
|
+
# do the matches:
|
41
|
+
allow = ::Baurets::Optionsful::Introspections.do_the_matches(routes, route_guess)
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_help_link(env)
|
45
|
+
server_name = env["SERVER_NAME"]
|
46
|
+
server_port = env["SERVER_PORT"]
|
47
|
+
"<http://#{server_name}:#{server_port}" + @config.base_path + "#{env["PATH_INFO"]}>; type=text/html; rel=help"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/optionsful.rb
ADDED
data/rails/init.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# Adding Optionsful to the Rack middleware stack:
|
2
|
+
ActionController::Dispatcher.middleware.use Baurets::Optionsful::Server
|
3
|
+
|
4
|
+
puts "Gem.searcher.find('optionsful').full_gem_path = #{Gem.searcher.find('optionsful').full_gem_path}"
|
5
|
+
|
6
|
+
Dir["#{Gem.searcher.find('optionsful').full_gem_path}/**/tasks/*.rake"].each do |ext|
|
7
|
+
puts "Loading: #{ext}"
|
8
|
+
load ext
|
9
|
+
end
|
File without changes
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Baurets::Optionsful::Config do
|
4
|
+
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
context "Config carries specific settings" do
|
8
|
+
|
9
|
+
describe "try to find the custom configuration file" do
|
10
|
+
|
11
|
+
it "if the custom configuration file exist, try to load settings from it" do
|
12
|
+
config = Baurets::Optionsful::Config.new( {:file => File.join(File.dirname(__FILE__), 'fixtures', 'optionsful.yml'), :environment => "test" })
|
13
|
+
config.base_path.should == "/test"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "if the custom configuration file exist and is not valid, keep the default settings" do
|
17
|
+
config = Baurets::Optionsful::Config.new( {:file => File.join(File.dirname(__FILE__), 'fixtures', 'optionsful_bug.yml'), :environment => "test" })
|
18
|
+
config.base_path.should == "/optionsful"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "if no configuration file is informed or found, do load the default settings" do
|
22
|
+
config = Baurets::Optionsful::Config.new
|
23
|
+
config.base_path.should == "/optionsful"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "if RAILS_ROOT is defined, look for the custom configuration file on its config folder" do
|
27
|
+
Baurets::Optionsful::Config.const_set(:RAILS_ROOT, File.dirname(__FILE__))
|
28
|
+
config = Baurets::Optionsful::Config.new
|
29
|
+
config.base_path.should == "/optionsful"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "and does not exist, keep default settings" do
|
33
|
+
config = Baurets::Optionsful::Config.new( {:file => File.join(File.dirname(__FILE__), 'fixtures', 'optionsful_xxx.yml'), :environment => "test" })
|
34
|
+
config.base_path.should == "/optionsful"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,335 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe Baurets::Optionsful::Server do
|
5
|
+
|
6
|
+
include Rack::Test::Methods
|
7
|
+
|
8
|
+
context "as a Rack middleware" do
|
9
|
+
|
10
|
+
it "is a Ruby object that responds to call;" do
|
11
|
+
assert ::Baurets::Optionsful::Server.new(app).respond_to? :call
|
12
|
+
end
|
13
|
+
|
14
|
+
it "takes exactly one argument, (the environment) and returns an Array;" do
|
15
|
+
response = ::Baurets::Optionsful::Server.new(app).call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
16
|
+
assert response.kind_of?(Array)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "the returned Array must have exactly three values: the status, the headers and the body;" do
|
20
|
+
response = ::Baurets::Optionsful::Server.new(app).call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
21
|
+
assert response.size.should == 3
|
22
|
+
assert response[0].kind_of? Fixnum
|
23
|
+
assert response[1].kind_of? Hash
|
24
|
+
assert response[2].kind_of? String
|
25
|
+
end
|
26
|
+
|
27
|
+
before do
|
28
|
+
ActionController::Routing::Routes.draw do |map|
|
29
|
+
map.resources :posts, :has_many => :comments
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "must be nice, acting somewhere on a Rack middleware stack;" do
|
34
|
+
response = fake_opts_app.call(mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => "/posts"}))
|
35
|
+
assert response.size.should == 3
|
36
|
+
assert response[0].kind_of? Fixnum
|
37
|
+
assert response[0].should == 204
|
38
|
+
assert response[1].kind_of? Hash
|
39
|
+
assert response[1]["Allow"]
|
40
|
+
end
|
41
|
+
|
42
|
+
it "must let the request go through the stack, if it has nothing to it!" do
|
43
|
+
response = fake_opts_app.call(mock_env({"REQUEST_METHOD" => "GET", "PATH_INFO" => "/lobster"}))
|
44
|
+
assert response.size.should == 3
|
45
|
+
assert response[0].kind_of? Fixnum
|
46
|
+
assert response[0].should == 200
|
47
|
+
assert response[1].kind_of? Hash
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
context "as an interpreter for HTTP OPTIONS requests, MUST recognize the Rails" do
|
53
|
+
|
54
|
+
describe "default resource routing" do
|
55
|
+
#Sample resource route (maps HTTP verbs to controller actions automatically)
|
56
|
+
|
57
|
+
before(:all) do
|
58
|
+
ActionController::Routing::Routes.draw do |map|
|
59
|
+
map.resources :posts, :has_many => :comments
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "the index action displays a list of all posts in response of a GET request" do
|
64
|
+
response = http_options_request("/posts")
|
65
|
+
assert response.kind_of?(Array)
|
66
|
+
assert allows?(response[1], "GET")
|
67
|
+
end
|
68
|
+
|
69
|
+
it "the new action return from a GET request an HTML form for creating a new post" do
|
70
|
+
response = http_options_request("/posts/new")
|
71
|
+
assert response.kind_of?(Array)
|
72
|
+
assert allows?(response[1], "GET")
|
73
|
+
end
|
74
|
+
|
75
|
+
it "the create action uses POST to create a new post instance" do
|
76
|
+
response = http_options_request("/posts")
|
77
|
+
assert response.kind_of?(Array)
|
78
|
+
assert allows?(response[1], "POST")
|
79
|
+
end
|
80
|
+
|
81
|
+
it "the show action display a specific post in response of a GET request" do
|
82
|
+
response = http_options_request("/posts/1")
|
83
|
+
assert response.kind_of?(Array)
|
84
|
+
assert allows?(response[1], "GET")
|
85
|
+
end
|
86
|
+
|
87
|
+
it "the edit action return an HTML form for editing a post in response of a GET request" do
|
88
|
+
response = http_options_request("/posts/1/edit")
|
89
|
+
assert response.kind_of?(Array)
|
90
|
+
assert allows?(response[1], "GET")
|
91
|
+
end
|
92
|
+
|
93
|
+
it "the update action uses PUT to update a specific post" do
|
94
|
+
response = http_options_request("/posts/1")
|
95
|
+
assert response.kind_of?(Array)
|
96
|
+
assert allows?(response[1], "PUT")
|
97
|
+
end
|
98
|
+
|
99
|
+
it "the destroy action uses DELETE to delete a specific post" do
|
100
|
+
response = http_options_request("/posts/1")
|
101
|
+
assert response.kind_of?(Array)
|
102
|
+
assert allows?(response[1], "DELETE")
|
103
|
+
end
|
104
|
+
|
105
|
+
it "not finding a path, return 404 Not Found" do
|
106
|
+
response = http_options_request("/sblingers/sblongers")
|
107
|
+
assert response.kind_of?(Array)
|
108
|
+
assert response[0].should be 404
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Note that extension relation types are REQUIRED to be absolute URIs
|
113
|
+
# in Link headers, and MUST be quoted if they contain a semicolon (";")
|
114
|
+
# or comma (",") (as these characters are used as delimiters in the
|
115
|
+
# header itself).
|
116
|
+
it "the Link header MUST be quoted if it contains a semicolon or comma" do
|
117
|
+
response = http_options_request("/posts")
|
118
|
+
assert response.kind_of?(Array)
|
119
|
+
link = response[1]["Link"]
|
120
|
+
assert link.should =~ /\A"{1}.+"\z/
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "named routes" do
|
126
|
+
|
127
|
+
before(:each) do
|
128
|
+
ActionController::Routing::Routes.draw do |map|
|
129
|
+
map.login 'login', :controller => 'accounts', :action => 'login'
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should work" do
|
134
|
+
response = http_options_request("/login")
|
135
|
+
assert response.kind_of?(Array)
|
136
|
+
assert response[0].should be 204
|
137
|
+
assert allows?(response[1], "GET") # WTF? return ANY?!? ;p
|
138
|
+
end
|
139
|
+
|
140
|
+
after(:all) do
|
141
|
+
ActionController::Routing::Routes.reload!
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "route globbing" do
|
147
|
+
|
148
|
+
before(:all) do
|
149
|
+
ActionController::Routing::Routes.draw do |map|
|
150
|
+
map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should work" do
|
155
|
+
pending "What the hell is that?"
|
156
|
+
response = http_options_request("/post/path")
|
157
|
+
assert response.kind_of?(Array)
|
158
|
+
assert response[0].should_not be 404
|
159
|
+
end
|
160
|
+
|
161
|
+
after(:all) do
|
162
|
+
ActionController::Routing::Routes.reload!
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "route conditions" do
|
168
|
+
|
169
|
+
before(:all) do
|
170
|
+
ActionController::Routing::Routes.draw do |map|
|
171
|
+
map.connect 'post/:id', :controller => 'posts', :action => 'show', :conditions => { :method => :get }
|
172
|
+
map.connect 'post/:id', :controller => 'posts', :action => 'create_comment', :conditions => { :method => :post }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should work" do
|
177
|
+
response = http_options_request("/post/123")
|
178
|
+
assert response.kind_of?(Array)
|
179
|
+
assert allows?(response[1], "GET")
|
180
|
+
assert allows?(response[1], "POST")
|
181
|
+
end
|
182
|
+
|
183
|
+
after(:all) do
|
184
|
+
ActionController::Routing::Routes.reload!
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
describe "pretty URLs" do
|
189
|
+
|
190
|
+
before(:all) do
|
191
|
+
ActionController::Routing::Routes.draw do |map|
|
192
|
+
map.connect "articles/:year/:month/:day", :controller => 'articles', :action => 'find_by_date', :requirements => { :year => /\d{4}/, :month => /\d{1,2}/, :day => /\d{1,2}/ }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should work" do
|
197
|
+
response = http_options_request("/articles/2010/07/23")
|
198
|
+
assert response.kind_of?(Array)
|
199
|
+
assert response[0].should be 204
|
200
|
+
end
|
201
|
+
|
202
|
+
after(:all) do
|
203
|
+
ActionController::Routing::Routes.reload!
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "regular expressions and parameters" do
|
209
|
+
|
210
|
+
before(:all) do
|
211
|
+
ActionController::Routing::Routes.draw do |map|
|
212
|
+
map.geocode 'geocode/:postalcode', :controller => 'geocode', :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should work" do
|
217
|
+
response = http_options_request("/geocode/20100")
|
218
|
+
assert response.kind_of?(Array)
|
219
|
+
assert response[0].should be 204
|
220
|
+
assert allows?(response[1], "GET")
|
221
|
+
end
|
222
|
+
|
223
|
+
after(:all) do
|
224
|
+
ActionController::Routing::Routes.reload!
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe "resource route within a namespace" do
|
229
|
+
|
230
|
+
before(:all) do
|
231
|
+
ActionController::Routing::Routes.draw do |map|
|
232
|
+
map.namespace :admin do |admin|
|
233
|
+
# Directs /admin/posts/* to Admin::PostsController (app/controllers/admin/posts_controller.rb)
|
234
|
+
admin.resources :posts
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
it "must understand an namespaced path" do
|
240
|
+
response = http_options_request("/admin/posts")
|
241
|
+
assert response.kind_of?(Array)
|
242
|
+
assert response[0].should be 204
|
243
|
+
assert allows?(response[1], "GET")
|
244
|
+
assert allows?(response[1], "POST")
|
245
|
+
end
|
246
|
+
|
247
|
+
after(:all) do
|
248
|
+
ActionController::Routing::Routes.reload!
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
|
253
|
+
describe "deal the catch all" do
|
254
|
+
|
255
|
+
before(:all) do
|
256
|
+
ActionController::Routing::Routes.draw do |map|
|
257
|
+
map.resources :posts
|
258
|
+
map.connect ':controller/:action/:id'
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
it "must cheat :P" do
|
263
|
+
response = http_options_request("/posts")
|
264
|
+
assert response.kind_of?(Array)
|
265
|
+
assert response[0].should be 204
|
266
|
+
assert allows?(response[1], "GET")
|
267
|
+
assert allows?(response[1], "POST")
|
268
|
+
end
|
269
|
+
|
270
|
+
after(:all) do
|
271
|
+
ActionController::Routing::Routes.reload!
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
|
276
|
+
describe "another sample of named route" do
|
277
|
+
before(:all) do
|
278
|
+
ActionController::Routing::Routes.draw do |map|
|
279
|
+
map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
it "should work" do
|
284
|
+
response = http_options_request("/products/123/purchase")
|
285
|
+
assert response.kind_of?(Array)
|
286
|
+
assert response[0].should be 204
|
287
|
+
assert allows?(response[1], "ANY")
|
288
|
+
end
|
289
|
+
|
290
|
+
after(:all) do
|
291
|
+
ActionController::Routing::Routes.reload!
|
292
|
+
end
|
293
|
+
|
294
|
+
end
|
295
|
+
|
296
|
+
describe "Sample resource route with options" do
|
297
|
+
|
298
|
+
before(:all) do
|
299
|
+
ActionController::Routing::Routes.draw do |map|
|
300
|
+
map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get }
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
it "should work" do
|
305
|
+
response = http_options_request("/products/123/toggle")
|
306
|
+
assert response.kind_of?(Array)
|
307
|
+
assert response[0].should be 204
|
308
|
+
assert allows?(response[1], "POST")
|
309
|
+
end
|
310
|
+
|
311
|
+
it "should work" do
|
312
|
+
response = http_options_request("/products/123/short")
|
313
|
+
assert response.kind_of?(Array)
|
314
|
+
assert response[0].should be 204
|
315
|
+
assert allows?(response[1], "GET")
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should work" do
|
319
|
+
response = http_options_request("/products/sold")
|
320
|
+
assert response.kind_of?(Array)
|
321
|
+
assert response[0].should be 204
|
322
|
+
assert allows?(response[1], "GET")
|
323
|
+
end
|
324
|
+
|
325
|
+
after(:all) do
|
326
|
+
ActionController::Routing::Routes.reload!
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
end
|
334
|
+
|
335
|
+
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', "baurets", 'optionsful', "config.rb")
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', "baurets", 'optionsful', "server.rb")
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', "baurets", 'optionsful', "introspections.rb")
|
4
|
+
# TODO ^ this was ugly?! :-S
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'sinatra'
|
8
|
+
require 'rack/test'
|
9
|
+
require 'rack'
|
10
|
+
require 'rack/lobster'
|
11
|
+
require 'spec'
|
12
|
+
require 'spec/autorun'
|
13
|
+
require 'spec/interop/test'
|
14
|
+
require 'action_controller'
|
15
|
+
|
16
|
+
|
17
|
+
# set test environment
|
18
|
+
set :environment, :test
|
19
|
+
set :run, false
|
20
|
+
set :raise_errors, true
|
21
|
+
set :logging, true
|
22
|
+
|
23
|
+
DEFAULT_ENV = { "rack.version" => Rack::VERSION, "rack.input" => StringIO.new, "rack.errors" => StringIO.new,
|
24
|
+
"rack.multithread" => true, "rack.multiprocess" => true, "rack.run_once" => false, }
|
25
|
+
|
26
|
+
SAMPLE_ENV = {"rack.session"=>{:session_id=>"f66c8af1e89f318775e923159e046e71"}, "SERVER_NAME"=>"localhost",
|
27
|
+
"HTTP_HOST"=>"localhost:3332", "REMOTE_HOST"=>"localhost", "HTTP_USER_AGENT"=>"HTTP%20Client0.9.1
|
28
|
+
CFNetwork/438.14 Darwin/9.8.0 (i386) (MacBook4%2C1)", "rack.url_scheme"=>"http", "REQUEST_PATH"=>"/",
|
29
|
+
"SERVER_PROTOCOL"=>"HTTP/1.1", "rack.errors"=>"", "PATH_INFO"=>"/", "REMOTE_ADDR"=>"127.0.0.1",
|
30
|
+
"SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.8.7/2010-06-23)", "rack.run_once"=>false, "rack.version"=>[1, 1],
|
31
|
+
"SCRIPT_NAME"=>"", "rack.multithread"=>false, "HTTP_VERSION"=>"HTTP/1.1", "REQUEST_URI"=>"",
|
32
|
+
"rack.multiprocess"=>false, "SERVER_PORT"=>"3332", "rack.session.options"=>{:domain=>"nilly", :path=>"/",
|
33
|
+
:key=>"_session_id", :httponly=>true, :expire_after=>300, :id=>"f66c8af1e89f318775e923159e046e71"},
|
34
|
+
"REQUEST_METHOD"=>"HEAD", "GATEWAY_INTERFACE"=>"CGI/1.1", "QUERY_STRING"=>"", "HTTP_CONNECTION"=>"close"}
|
35
|
+
|
36
|
+
FAKE_ENV = SAMPLE_ENV.merge!(DEFAULT_ENV)
|
37
|
+
|
38
|
+
def app
|
39
|
+
app = Rack::Builder.new {
|
40
|
+
use Rack::CommonLogger
|
41
|
+
use Rack::ShowExceptions
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def fake_opts_app
|
47
|
+
app = Rack::Builder.new {
|
48
|
+
use Rack::CommonLogger
|
49
|
+
use Rack::ShowExceptions
|
50
|
+
use Baurets::Optionsful::Server
|
51
|
+
map "/lobster" do
|
52
|
+
use Rack::Lint
|
53
|
+
run Rack::Lobster.new
|
54
|
+
end
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def mock_env(options = {})
|
59
|
+
FAKE_ENV.merge!(options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def http_options_request(path)
|
63
|
+
complex_env = mock_env({"REQUEST_METHOD" => "OPTIONS", "PATH_INFO" => path })
|
64
|
+
response = Baurets::Optionsful::Server.new(app).call(complex_env)
|
65
|
+
end
|
66
|
+
|
67
|
+
def allows?(headers, method)
|
68
|
+
headers["Allow"].include?(method)
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: optionsful2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 4
|
10
|
+
version: 0.2.4
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Marco Antonio Gonzalez Junior
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-08-05 00:00:00 -03:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Help building RESTful web services enabling HTTP OPTIONS verb on Ruby on Rails applications.
|
23
|
+
email: kayaman@baurets.net
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- README.textile
|
32
|
+
- MIT-LICENSE
|
33
|
+
- Rakefile
|
34
|
+
- lib/optionsful.rb
|
35
|
+
- lib/baurets/optionsful/server.rb
|
36
|
+
- lib/baurets/optionsful/introspections.rb
|
37
|
+
- lib/baurets/optionsful/config.rb
|
38
|
+
- lib/baurets/optionsful/version.rb
|
39
|
+
- lib/tasks/optionsful.rake
|
40
|
+
- rails/init.rb
|
41
|
+
- samples/optionsful.yml
|
42
|
+
- spec/fixtures/optionsful.yml
|
43
|
+
- spec/fixtures/optionsful_bug.yml
|
44
|
+
- spec/optionsful_server_spec.rb
|
45
|
+
- spec/optionsful_config_spec.rb
|
46
|
+
- spec/spec.opts
|
47
|
+
- spec/spec_helper.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/kayaman/optionsful2
|
50
|
+
licenses: []
|
51
|
+
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.3.7
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: Support HTTP OPTIONS verb on your Rails app.
|
82
|
+
test_files: []
|
83
|
+
|