optionsful2 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -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.
@@ -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
@@ -0,0 +1,16 @@
1
+ module Baurets
2
+ module Optionsful
3
+ module Version
4
+
5
+ MAJOR = 0
6
+ MINOR = 2
7
+ TINY = 4
8
+
9
+ def self.to_s
10
+ "#{MAJOR}.#{MINOR}.#{TINY}"
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__))
2
+
3
+ require 'baurets/optionsful/config'
4
+ require 'baurets/optionsful/introspections'
5
+ require 'baurets/optionsful/server'
6
+
7
+ module Optionsful
8
+
9
+
10
+ end
@@ -0,0 +1,8 @@
1
+ require 'fileutils'
2
+
3
+ namespace :optionsful do
4
+ desc "Install 'config/optionsful.yml'"
5
+ task :yml do
6
+ puts File.dirname(__FILE__)
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,14 @@
1
+ development:
2
+ introspection: enabled
3
+ http:
4
+ base_path: /blopts
5
+
6
+ test:
7
+ introspection: enabled
8
+ http:
9
+ base_path: /blopts
10
+
11
+ production:
12
+ introspection: enabled
13
+ http:
14
+ base_path: /blopts
@@ -0,0 +1,11 @@
1
+ development:
2
+ http:
3
+ base_path: /blopts
4
+
5
+ test:
6
+ http:
7
+ base_path: /test
8
+
9
+ production:
10
+ http:
11
+ base_path: /blopts
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
+
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -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
+