fredo 0.1.3
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +48 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/fredo.gemspec +61 -0
- data/lib/fredo.rb +76 -0
- data/lib/fredo/ext/mongrel_handler.rb +42 -0
- data/lib/fredo/ext/net_http.rb +99 -0
- data/lib/fredo/handler.rb +115 -0
- data/lib/fredo/registry.rb +52 -0
- data/lib/fredo/response.rb +19 -0
- data/lib/fredo/stub_socket.rb +15 -0
- data/spec/fredo_spec.rb +72 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +11 -0
- metadata +93 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Leandro Pedroni
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= Fredo
|
2
|
+
==== Stubs web services by plugging a sinatra-y DSL on Net::Http
|
3
|
+
|
4
|
+
I know it was you, Fredo[http://www.youtube.com/watch?v=FcFlp6kl508]
|
5
|
+
|
6
|
+
I needed a slightly more articulated stub behaviour than the one provided by fakeweb[http://github.com/chrisk/fakeweb] to mimic S3 uploads and external APIs to process my attachments.
|
7
|
+
Inside my simple cucumber steps.
|
8
|
+
|
9
|
+
== Install
|
10
|
+
On the console:
|
11
|
+
>> gem install fredo
|
12
|
+
|
13
|
+
In your app:
|
14
|
+
# config/environments/test.rb, config/environments/cucumber.rb
|
15
|
+
config.gem 'fredo'
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
|
19
|
+
I borrowed the sinatra router, fakeweb's Net::Http extension and glued them together with Rack like returns and accepts:
|
20
|
+
|
21
|
+
# and in your s3_steps.rb or what not:
|
22
|
+
Fredo.put "http://s3.amazonaws.com/*/:size.:ext" do
|
23
|
+
[200, {}, 'OK']
|
24
|
+
end
|
25
|
+
|
26
|
+
Fredo.get "http://s3.amazonaws.com/*/:size.:ext" do
|
27
|
+
fixture_path = File.expand_path("../../support/s3/placeholder_#{params[:size]}.#{params[:ext]}", __FILE__)
|
28
|
+
[200, {'ContentType' => "image/#{params[:ext]}"}, File.read]
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
== Bugs
|
33
|
+
This is in a very early stage for the project, feel free to provide feedback. Please use the Github issues.
|
34
|
+
|
35
|
+
|
36
|
+
== Note on Patches/Pull Requests
|
37
|
+
|
38
|
+
* Fork the project.
|
39
|
+
* Make your feature addition or bug fix.
|
40
|
+
* Add tests for it. This is important so I don't break it in a
|
41
|
+
future version unintentionally.
|
42
|
+
* Commit, do not mess with rakefile, version, or history.
|
43
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
44
|
+
* Send me a pull request. Bonus points for topic branches.
|
45
|
+
|
46
|
+
== Copyright
|
47
|
+
|
48
|
+
Copyright (c) 2010 Leandro Pedroni. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "fredo"
|
8
|
+
gem.summary = %Q{Mocks web services by plugging Net::Http straight into Rack}
|
9
|
+
gem.description = %Q{Mocks web services by plugging Net::Http straight into Rack}
|
10
|
+
gem.email = "ilpoldo@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/ilpoldo/fredo"
|
12
|
+
gem.authors = ["ilpoldo"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "fredo #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.3
|
data/fredo.gemspec
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{fredo}
|
8
|
+
s.version = "0.1.3"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["ilpoldo"]
|
12
|
+
s.date = %q{2010-03-24}
|
13
|
+
s.description = %q{Mocks web services by plugging Net::Http straight into Rack}
|
14
|
+
s.email = %q{ilpoldo@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"fredo.gemspec",
|
27
|
+
"lib/fredo.rb",
|
28
|
+
"lib/fredo/ext/mongrel_handler.rb",
|
29
|
+
"lib/fredo/ext/net_http.rb",
|
30
|
+
"lib/fredo/handler.rb",
|
31
|
+
"lib/fredo/registry.rb",
|
32
|
+
"lib/fredo/response.rb",
|
33
|
+
"lib/fredo/stub_socket.rb",
|
34
|
+
"spec/fredo_spec.rb",
|
35
|
+
"spec/spec.opts",
|
36
|
+
"spec/spec_helper.rb"
|
37
|
+
]
|
38
|
+
s.homepage = %q{http://github.com/ilpoldo/fredo}
|
39
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
40
|
+
s.require_paths = ["lib"]
|
41
|
+
s.rubygems_version = %q{1.3.6}
|
42
|
+
s.summary = %q{Mocks web services by plugging Net::Http straight into Rack}
|
43
|
+
s.test_files = [
|
44
|
+
"spec/fredo_spec.rb",
|
45
|
+
"spec/spec_helper.rb"
|
46
|
+
]
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
50
|
+
s.specification_version = 3
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
53
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
56
|
+
end
|
57
|
+
else
|
58
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
data/lib/fredo.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'uri'
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
require 'fredo/ext/net_http'
|
6
|
+
require 'fredo/stub_socket'
|
7
|
+
require 'fredo/registry'
|
8
|
+
require 'fredo/handler'
|
9
|
+
require 'fredo/response'
|
10
|
+
|
11
|
+
module Fredo
|
12
|
+
|
13
|
+
# Returns the version string for the Fredo you have loaded.
|
14
|
+
VERSION = File.exist?('VERSION') ? File.read('VERSION') : ""
|
15
|
+
|
16
|
+
# Resets Fredo's Registry. This will force all subsequent web requests to
|
17
|
+
# behave as real requests.
|
18
|
+
def self.clean_registry
|
19
|
+
Registry.instance.clean_registry
|
20
|
+
end
|
21
|
+
|
22
|
+
# Registers get url that will make Fredo an OK response or the response generated
|
23
|
+
# in the block passed to get.
|
24
|
+
def self.get(path, opts={}, &bk); Registry.route 'GET', path, opts, &bk end
|
25
|
+
def self.put(path, opts={}, &bk); Registry.route 'PUT', path, opts, &bk end
|
26
|
+
def self.post(path, opts={}, &bk); Registry.route 'POST', path, opts, &bk end
|
27
|
+
def self.delete(path, opts={}, &bk); Registry.route 'DELETE', path, opts, &bk end
|
28
|
+
def self.head(path, opts={}, &bk); Registry.route 'HEAD', path, opts, &bk end
|
29
|
+
|
30
|
+
# Enables or disables real HTTP connections for requests that don't match
|
31
|
+
# registered URIs.
|
32
|
+
#
|
33
|
+
# If you set <tt>Fredo.allow_net_connect = false</tt> and subsequently try
|
34
|
+
# to make a request to a URI you haven't registered with #register_uri, a
|
35
|
+
# NetConnectNotAllowedError will be raised. This is handy when you want to
|
36
|
+
# make sure your tests are self-contained, or want to catch the scenario
|
37
|
+
# when a URI is changed in implementation code without a corresponding test
|
38
|
+
# change.
|
39
|
+
#
|
40
|
+
# When <tt>Fredo.allow_net_connect = true</tt> (the default), requests to
|
41
|
+
# URIs not stubbed with Fredo are passed through to Net::HTTP.
|
42
|
+
def self.allow_net_connect=(allowed)
|
43
|
+
@allow_net_connect = allowed
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns +true+ if requests to URIs not registered with Fredo are passed
|
47
|
+
# through to Net::HTTP for normal processing (the default). Returns +false+
|
48
|
+
# if an exception is raised for these requests.
|
49
|
+
def self.allow_net_connect?
|
50
|
+
@allow_net_connect || true
|
51
|
+
end
|
52
|
+
|
53
|
+
# This exception is raised if you set <tt>Fredo.allow_net_connect =
|
54
|
+
# false</tt> and subsequently try to make a request to a URI you haven't
|
55
|
+
# stubbed.
|
56
|
+
class NetConnectNotAllowedError < StandardError
|
57
|
+
def initialize(uri)
|
58
|
+
super "Connection to #{uri} is disabled"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# This exception is raised if something goes wrong on Fredo's end during
|
63
|
+
# the test.
|
64
|
+
class Mixup < Exception; end;
|
65
|
+
|
66
|
+
# This exception is raised if Fredo can't match the request URI
|
67
|
+
class NotFound < Exception; end;
|
68
|
+
|
69
|
+
def self.call(env)
|
70
|
+
Handler.new.call(env)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.forget
|
74
|
+
Registry.clear
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# File lib/rack/handler/mongrel.rb
|
2
|
+
def process(request, response)
|
3
|
+
env = {}.replace(request.params)
|
4
|
+
env.delete "HTTP_CONTENT_TYPE"
|
5
|
+
env.delete "HTTP_CONTENT_LENGTH"
|
6
|
+
|
7
|
+
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
8
|
+
|
9
|
+
env.update({"rack.version" => [0,1],
|
10
|
+
"rack.input" => request.body || StringIO.new(""),
|
11
|
+
"rack.errors" => $stderr,
|
12
|
+
|
13
|
+
"rack.multithread" => true,
|
14
|
+
"rack.multiprocess" => false, # ???
|
15
|
+
"rack.run_once" => false,
|
16
|
+
|
17
|
+
"rack.url_scheme" => "http",
|
18
|
+
})
|
19
|
+
env["QUERY_STRING"] ||= ""
|
20
|
+
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
|
21
|
+
|
22
|
+
status, headers, body = @app.call(env)
|
23
|
+
|
24
|
+
begin
|
25
|
+
response.status = status.to_i
|
26
|
+
response.send_status(nil)
|
27
|
+
|
28
|
+
headers.each { |k, vs|
|
29
|
+
vs.split("\n").each { |v|
|
30
|
+
response.header[k] = v
|
31
|
+
}
|
32
|
+
}
|
33
|
+
response.send_header
|
34
|
+
|
35
|
+
body.each { |part|
|
36
|
+
response.write part
|
37
|
+
response.socket.flush
|
38
|
+
}
|
39
|
+
ensure
|
40
|
+
body.close if body.respond_to? :close
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Net #:nodoc: all
|
6
|
+
|
7
|
+
class BufferedIO
|
8
|
+
def initialize_with_fredo(io, debug_output = nil)
|
9
|
+
@read_timeout = 60
|
10
|
+
@rbuf = ''
|
11
|
+
@debug_output = debug_output
|
12
|
+
|
13
|
+
@io = case io
|
14
|
+
when Socket, OpenSSL::SSL::SSLSocket, IO
|
15
|
+
io
|
16
|
+
when String
|
17
|
+
if !io.include?("\0") && File.exists?(io) && !File.directory?(io)
|
18
|
+
File.open(io, "r")
|
19
|
+
else
|
20
|
+
StringIO.new(io)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
raise "Unable to create local socket" unless @io
|
24
|
+
end
|
25
|
+
alias_method :initialize_without_fredo, :initialize
|
26
|
+
alias_method :initialize, :initialize_with_fredo
|
27
|
+
end
|
28
|
+
|
29
|
+
class HTTP
|
30
|
+
# class << self
|
31
|
+
# def socket_type_with_fredo
|
32
|
+
# Fredo::StubSocket
|
33
|
+
# end
|
34
|
+
# alias_method :socket_type_without_fredo, :socket_type
|
35
|
+
# alias_method :socket_type, :socket_type_with_fredo
|
36
|
+
# end
|
37
|
+
|
38
|
+
def request_with_fredo(request, body = nil, &block)
|
39
|
+
request_body = body
|
40
|
+
|
41
|
+
uri = URI.parse(request.path)
|
42
|
+
protocol = use_ssl? ? "https" : "http"
|
43
|
+
full_path = "#{protocol}://#{self.address}:#{self.port}#{request.path}"
|
44
|
+
|
45
|
+
rack_env ={'REQUEST_METHOD' => request.method,
|
46
|
+
'SCRIPT_NAME' => '',
|
47
|
+
'PATH_INFO' => uri.path,
|
48
|
+
'QUERY_STRING' => (uri.query || ''),
|
49
|
+
'SERVER_NAME' => self.address,
|
50
|
+
'SERVER_PORT' => self.port,
|
51
|
+
'rack.version' => [1,1],
|
52
|
+
'rack.url_scheme' => protocol,
|
53
|
+
'rack.input' => body || StringIO.new,
|
54
|
+
'rack.errors' => $stderr,
|
55
|
+
'rack.multithread' => true,
|
56
|
+
'rack.multiprocess' => true,
|
57
|
+
'rack.run_once' => false}
|
58
|
+
|
59
|
+
# @socket = Net::HTTP.socket_type.new
|
60
|
+
# Perform the request
|
61
|
+
status, header, body = Fredo.call(rack_env)
|
62
|
+
# body = body.to_s # should write to rocket or do something fancier?
|
63
|
+
|
64
|
+
response = Net::HTTPResponse.send(:response_class, "#{status}").new("1.0", "#{status}", body)
|
65
|
+
response.instance_variable_set(:@body, body)
|
66
|
+
header.each { |name, value| response[name] = value }
|
67
|
+
response.instance_variable_set(:@read, true)
|
68
|
+
|
69
|
+
def response.read_body(*args, &block)
|
70
|
+
yield @body if block_given?
|
71
|
+
@body
|
72
|
+
end
|
73
|
+
|
74
|
+
yield response if block_given?
|
75
|
+
response
|
76
|
+
|
77
|
+
rescue Fredo::NotFound
|
78
|
+
|
79
|
+
raise Fredo::NetConnectNotAllowedError.new(full_path) unless Fredo.allow_net_connect?
|
80
|
+
connect_without_fredo
|
81
|
+
return request_without_fredo(request, request_body, &block)
|
82
|
+
|
83
|
+
end
|
84
|
+
alias_method :request_without_fredo, :request
|
85
|
+
alias_method :request, :request_with_fredo
|
86
|
+
|
87
|
+
|
88
|
+
def connect_with_fredo
|
89
|
+
unless @@alredy_checked_for_net_http_replacement_libs ||= false
|
90
|
+
# Fredo::Utility.puts_warning_for_net_http_replacement_libs_if_needed
|
91
|
+
@@alredy_checked_for_net_http_replacement_libs = true
|
92
|
+
end
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
alias_method :connect_without_fredo, :connect
|
96
|
+
alias_method :connect, :connect_with_fredo
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Fredo
|
2
|
+
class Handler #:nodoc:
|
3
|
+
|
4
|
+
attr_accessor :env, :request, :response, :params
|
5
|
+
|
6
|
+
def call(env)
|
7
|
+
@env = env
|
8
|
+
@request = Rack::Request.new(env)
|
9
|
+
@params = @request.params
|
10
|
+
@response = Response.new([],200,{})
|
11
|
+
|
12
|
+
dispatch!
|
13
|
+
|
14
|
+
respond!
|
15
|
+
end
|
16
|
+
|
17
|
+
# Dispatch a request with error handling.
|
18
|
+
def dispatch!
|
19
|
+
route!
|
20
|
+
end
|
21
|
+
|
22
|
+
def routes_for_host?
|
23
|
+
Registry.routes[@request.request_method][@request.host]
|
24
|
+
rescue
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def route!
|
29
|
+
|
30
|
+
if routes = routes_for_host?
|
31
|
+
path = URI.unescape(@request.path_info)
|
32
|
+
routes.each do |pattern, keys, conditions, block|
|
33
|
+
if match = pattern.match(path)
|
34
|
+
values = match.captures.to_a
|
35
|
+
params =
|
36
|
+
if keys.any?
|
37
|
+
keys.zip(values).inject({}) do |hash,(k,v)|
|
38
|
+
if k == :splat
|
39
|
+
(hash[k] ||= []) << v
|
40
|
+
else
|
41
|
+
hash[k.to_sym] = v
|
42
|
+
end
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
elsif values.any?
|
46
|
+
{'captures' => values}
|
47
|
+
else
|
48
|
+
{}
|
49
|
+
end
|
50
|
+
@params.merge!(params)
|
51
|
+
perform!(&block)
|
52
|
+
return
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
raise NotFound
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_not_found!(boom)
|
62
|
+
@env['fredo.error'] = boom
|
63
|
+
@response.status = 404
|
64
|
+
@response.body = ['<h1>Not Found</h1>']
|
65
|
+
end
|
66
|
+
|
67
|
+
def handle_exception!(boom)
|
68
|
+
@env['fredo.error'] = boom
|
69
|
+
@response.body = ["<h1>You broke my heart Fredo!</h1><h5>Fredo raised an exception.</h5><tt>#{boom.inspect}</tt>"]
|
70
|
+
@response.status = 500
|
71
|
+
# raise Mixup.new("original error: #{boom.inspect}") if Fredo.allow_exceptions?
|
72
|
+
end
|
73
|
+
|
74
|
+
def perform!(&block)
|
75
|
+
if block_given?
|
76
|
+
@response['Content-Type']= 'text/html'
|
77
|
+
|
78
|
+
res = instance_eval(&block)
|
79
|
+
case
|
80
|
+
when res.respond_to?(:to_str)
|
81
|
+
@response.body = [res]
|
82
|
+
when res.respond_to?(:to_ary)
|
83
|
+
res = res.to_ary
|
84
|
+
if Fixnum === res.first
|
85
|
+
if res.length == 3
|
86
|
+
@response.status, headers, body = res
|
87
|
+
@response.body = body if body
|
88
|
+
headers.each { |k, v| @response.headers[k] = v } if headers
|
89
|
+
elsif res.length == 2
|
90
|
+
@response.status = res.first
|
91
|
+
@response.body = res.last
|
92
|
+
else
|
93
|
+
raise TypeError, "#{res.inspect} not supported"
|
94
|
+
end
|
95
|
+
else
|
96
|
+
@response.body = res
|
97
|
+
end
|
98
|
+
when res.respond_to?(:each)
|
99
|
+
@response.body = res
|
100
|
+
when (100...599) === res
|
101
|
+
@response.status = res
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
rescue ::Exception => boom
|
106
|
+
handle_exception!(boom)
|
107
|
+
end
|
108
|
+
|
109
|
+
def respond!
|
110
|
+
status, header, body = @response.finish
|
111
|
+
[status, header, body]
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Fredo
|
2
|
+
class Registry #:nodoc:
|
3
|
+
|
4
|
+
def self.route(verb, uri, options={}, &block)
|
5
|
+
uri = URI.parse(uri)
|
6
|
+
uri.path = '/' if uri.path.empty?
|
7
|
+
pattern, keys = compile(uri.path)
|
8
|
+
|
9
|
+
@routes ||={}
|
10
|
+
@routes[verb] ||= {}
|
11
|
+
(@routes[verb][uri.host] ||= []).
|
12
|
+
push([pattern, keys, options, block]).last
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.routes
|
16
|
+
@routes
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.clear
|
20
|
+
@routes = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.compile(path)
|
24
|
+
keys = []
|
25
|
+
if path.respond_to? :to_str
|
26
|
+
special_chars = %w{. + ( )}
|
27
|
+
pattern =
|
28
|
+
path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
|
29
|
+
case match
|
30
|
+
when "*"
|
31
|
+
keys << 'splat'
|
32
|
+
"(.*?)"
|
33
|
+
when *special_chars
|
34
|
+
Regexp.escape(match)
|
35
|
+
else
|
36
|
+
keys << $2[1..-1]
|
37
|
+
"([^/?&#]+)"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
[/^#{pattern}$/, keys]
|
41
|
+
elsif path.respond_to?(:keys) && path.respond_to?(:match)
|
42
|
+
[path, path.keys]
|
43
|
+
elsif path.respond_to? :match
|
44
|
+
[path, keys]
|
45
|
+
else
|
46
|
+
raise TypeError, path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Fredo
|
2
|
+
class Response < Rack::Response
|
3
|
+
def finish
|
4
|
+
@body = block if block_given?
|
5
|
+
if [204, 304].include?(status.to_i)
|
6
|
+
header.delete "Content-Type"
|
7
|
+
[status.to_i, header.to_hash, []]
|
8
|
+
else
|
9
|
+
body = @body || []
|
10
|
+
body = [body] if body.respond_to? :to_str
|
11
|
+
if body.respond_to?(:to_ary)
|
12
|
+
header["Content-Length"] = body.to_ary.
|
13
|
+
inject(0) { |len, part| len + part.bytesize }.to_s
|
14
|
+
end
|
15
|
+
[status.to_i, header.to_hash, body]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/spec/fredo_spec.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Fredo do
|
4
|
+
context "initialization" do
|
5
|
+
|
6
|
+
it "loads"
|
7
|
+
|
8
|
+
it "hijacks Net::Http"
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
context "request handling" do
|
13
|
+
|
14
|
+
it "falls back to normal behaviour" do
|
15
|
+
response = open('http://www.google.com')
|
16
|
+
response.status.first.should eql('200')
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can hijack a request and return a value"
|
20
|
+
|
21
|
+
it "can prevent external resources to be called"
|
22
|
+
|
23
|
+
it "can hijack a get request" do
|
24
|
+
body = "Today ain't your lucky day, kid."
|
25
|
+
|
26
|
+
Fredo.get 'http://www.google.com' do
|
27
|
+
body
|
28
|
+
end
|
29
|
+
|
30
|
+
response = open('http://www.google.com')
|
31
|
+
response.read.should eql(body)
|
32
|
+
# response.header["Content-Type"].should eql("text/html")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "registry" do
|
37
|
+
|
38
|
+
it "behaves" do
|
39
|
+
Fredo.get 'http://google.com'
|
40
|
+
Fredo::Registry.routes['GET'].should_not be_empty
|
41
|
+
end
|
42
|
+
|
43
|
+
it "is clears in every spec" do
|
44
|
+
# Automate?
|
45
|
+
Fredo.forget
|
46
|
+
Fredo::Registry.routes.should be_empty
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
context "parameters" do
|
52
|
+
it "parses url parameters" do
|
53
|
+
|
54
|
+
Fredo.get 'http://www.twitter.com/:name' do
|
55
|
+
"It ain't #{params[:name]} talking"
|
56
|
+
end
|
57
|
+
|
58
|
+
response = open('http://www.twitter.com/sam')
|
59
|
+
response.read.should include('It ain\'t sam')
|
60
|
+
end
|
61
|
+
|
62
|
+
it "plays nice with regex" do
|
63
|
+
Fredo.get 'http://www.google.com/*' do
|
64
|
+
"Everything google!"
|
65
|
+
end
|
66
|
+
|
67
|
+
response = open('http://google.com/something')
|
68
|
+
response.read.should include('Everything google!')
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fredo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 3
|
9
|
+
version: 0.1.3
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- ilpoldo
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-24 00:00:00 +00:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 2
|
30
|
+
- 9
|
31
|
+
version: 1.2.9
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
description: Mocks web services by plugging Net::Http straight into Rack
|
35
|
+
email: ilpoldo@gmail.com
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- LICENSE
|
42
|
+
- README.rdoc
|
43
|
+
files:
|
44
|
+
- .document
|
45
|
+
- .gitignore
|
46
|
+
- LICENSE
|
47
|
+
- README.rdoc
|
48
|
+
- Rakefile
|
49
|
+
- VERSION
|
50
|
+
- fredo.gemspec
|
51
|
+
- lib/fredo.rb
|
52
|
+
- lib/fredo/ext/mongrel_handler.rb
|
53
|
+
- lib/fredo/ext/net_http.rb
|
54
|
+
- lib/fredo/handler.rb
|
55
|
+
- lib/fredo/registry.rb
|
56
|
+
- lib/fredo/response.rb
|
57
|
+
- lib/fredo/stub_socket.rb
|
58
|
+
- spec/fredo_spec.rb
|
59
|
+
- spec/spec.opts
|
60
|
+
- spec/spec_helper.rb
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: http://github.com/ilpoldo/fredo
|
63
|
+
licenses: []
|
64
|
+
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options:
|
67
|
+
- --charset=UTF-8
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
version: "0"
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.3.6
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Mocks web services by plugging Net::Http straight into Rack
|
91
|
+
test_files:
|
92
|
+
- spec/fredo_spec.rb
|
93
|
+
- spec/spec_helper.rb
|