mdub-sham_rack 1.0.0

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/README.markdown ADDED
@@ -0,0 +1,69 @@
1
+ ShamRack
2
+ ========
3
+
4
+ ShamRack plumbs Net:HTTP into [Rack][rack], providing infrastructure for testing arbitrary Rack-based apps using arbitrary HTTP clients, all from the comfort of your own Ruby VM.
5
+
6
+ What's it for, again?
7
+ ---------------------
8
+
9
+ Well, you can _test your HTTP client code_, using ShamRack to fake out an external web-service ([Sinatra][sinatra] helps, here).
10
+
11
+ Or, you can `ShamRack.mount` your actual Sinatra/Rails/Merb/Rack app, which is handy if you want to _test access using an HTTP client library_ such as:
12
+
13
+ * [`rest-client`][rest-client]
14
+ * [`httparty`][httparty]
15
+ * [`oauth`][oauth]
16
+
17
+ Installing it
18
+ -------------
19
+
20
+ gem sources -a http://gems.github.com
21
+ sudo gem install mdub-sham_rack
22
+
23
+ Using it
24
+ --------
25
+
26
+ require 'sham_rack'
27
+
28
+ rack_app = lambda { |env| ["200 OK", { "Content-type" => "text/plain" }, "Hello, world!"] }
29
+ ShamRack.mount(rack_app, "www.example.com")
30
+
31
+ require 'open-uri'
32
+ open("http://www.example.com/").read #=> "Hello, world!"
33
+
34
+ ### Sinatra integration
35
+
36
+ ShamRack.sinatra("sinatra.xyz") do
37
+ get "/hello/:subject" do
38
+ "Hello, #{params[:subject]}"
39
+ end
40
+ end
41
+
42
+ open("http://sinatra.xyz/hello/stranger").read #=> "Hello, stranger"
43
+
44
+ ### Rackup support
45
+
46
+ ShamRack.rackup("rackup.xyz") do
47
+ use Some::Middleware
48
+ use Some::Other::Middleware
49
+ run MyApp.new
50
+ end
51
+
52
+ What's the catch?
53
+ -----------------
54
+
55
+ * It's brand new! (there will be dragons)
56
+ * Your Rack request-handling code runs in the same Ruby VM, in fact the same Thread, as your request.
57
+
58
+ Thanks to
59
+ ---------
60
+
61
+ * Blaine Cook for [FakeWeb][fakeweb], which was an inspiration for ShamRack.
62
+ * Christian Neukirchen et al for the chewy goodness that is [Rack][rack].
63
+
64
+ [rack]: http://rack.rubyforge.org/
65
+ [sinatra]: http://www.sinatrarb.com/
66
+ [rest-client]: http://github.com/adamwiggins/rest-client
67
+ [httparty]: http://github.com/jnunemaker/httparty
68
+ [oauth]: http://oauth.rubyforge.org/
69
+ [fakeweb]: http://fakeweb.rubyforge.org/
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require "spec/rake/spectask"
2
+
3
+ task "default" => "spec"
4
+
5
+ Spec::Rake::SpecTask.new do |t|
6
+ t.spec_opts = ["--colour", "--format", "progress"]
7
+ t.spec_files = FileList['spec/**/*_spec.rb']
8
+ end
9
+
10
+ begin
11
+ require 'jeweler'
12
+ Jeweler::Tasks.new do |gemspec|
13
+ gemspec.name = "sham_rack"
14
+ gemspec.summary = "Net::HTTP-to-Rack plumbing"
15
+ gemspec.email = "mdub@dogbiscuit.org"
16
+ gemspec.homepage = "http://github.com/mdub/sham_rack"
17
+ gemspec.description = "ShamRack plumbs Net::HTTP directly into Rack, for quick and easy HTTP testing."
18
+ gemspec.authors = ["Mike Williams"]
19
+ end
20
+ rescue LoadError
21
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
22
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 1
4
+ :minor: 0
@@ -0,0 +1,17 @@
1
+ require "net/http"
2
+ require "sham_rack/registry"
3
+ require "sham_rack/http"
4
+
5
+ module Net
6
+
7
+ def HTTP.new(address, port = nil, *proxy_args)
8
+ port ||= HTTP.default_port
9
+ rack_app = ShamRack.application_for(address, port)
10
+ if rack_app
11
+ ShamRack::HTTP.new(address, port, rack_app)
12
+ else
13
+ super(address, port, *proxy_args)
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,106 @@
1
+ module ShamRack
2
+
3
+ # a sham version of Net::HTTP
4
+ class HTTP
5
+
6
+ def initialize(address, port, rack_app)
7
+ @address = address
8
+ @port = port
9
+ @rack_app = rack_app
10
+ end
11
+
12
+ def start
13
+ yield self
14
+ end
15
+
16
+ attr_accessor :use_ssl, :verify_mode, :read_timeout, :open_timeout
17
+
18
+ def request(req, body = nil)
19
+ env = default_env
20
+ env.merge!(path_env(req.path))
21
+ env.merge!(method_env(req))
22
+ env.merge!(header_env(req))
23
+ env.merge!(io_env(req, body))
24
+ response = build_response(@rack_app.call(env))
25
+ yield response if block_given?
26
+ return response
27
+ end
28
+
29
+ private
30
+
31
+ def default_env
32
+ {
33
+ "SCRIPT_NAME" => "",
34
+ "SERVER_NAME" => @address,
35
+ "SERVER_PORT" => @port.to_s,
36
+ "rack.version" => [0,1],
37
+ "rack.url_scheme" => "http",
38
+ "rack.multithread" => true,
39
+ "rack.multiprocess" => true,
40
+ "rack.run_once" => false
41
+ }
42
+ end
43
+
44
+ def method_env(request)
45
+ {
46
+ "REQUEST_METHOD" => request.method
47
+ }
48
+ end
49
+
50
+ def io_env(request, body)
51
+ raise(ArgumentError, "both request.body and body argument were provided") if (request.body && body)
52
+ body ||= request.body || ""
53
+ {
54
+ "rack.input" => StringIO.new(body),
55
+ "rack.errors" => $stderr
56
+ }
57
+ end
58
+
59
+ def path_env(path)
60
+ uri = URI.parse(path)
61
+ {
62
+ "PATH_INFO" => uri.path,
63
+ "QUERY_STRING" => (uri.query || ""),
64
+ }
65
+ end
66
+
67
+ def header_env(request)
68
+ result = {}
69
+ request.each do |header, content|
70
+ result["HTTP_" + header.upcase.gsub('-', '_')] = content
71
+ end
72
+ %w(TYPE LENGTH).each do |x|
73
+ result["CONTENT_#{x}"] = result.delete("HTTP_CONTENT_#{x}") if result.has_key?("HTTP_CONTENT_#{x}")
74
+ end
75
+ return result
76
+ end
77
+
78
+ def build_response(rack_response)
79
+ status, headers, body = rack_response
80
+ code, message = status.to_s.split(" ", 2)
81
+ response = Net::HTTPResponse.send(:response_class, code).new("Sham", code, message)
82
+ response.instance_variable_set(:@body, assemble_body(body))
83
+ response.instance_variable_set(:@read, true)
84
+ response.extend ShamRack::ResponseExtensions
85
+ return response
86
+ end
87
+
88
+ def assemble_body(body)
89
+ content = ""
90
+ body.each { |fragment| content << fragment }
91
+ return content
92
+ end
93
+
94
+ end
95
+
96
+ module ResponseExtensions
97
+
98
+ def read_body(dest = nil)
99
+ yield @body if block_given?
100
+ dest << @body if dest
101
+ return @body
102
+ end
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,45 @@
1
+ module ShamRack
2
+
3
+ module Registry
4
+
5
+ def mount(rack_app, address, port = nil)
6
+ port ||= Net::HTTP.default_port
7
+ registry[[address, port]] = rack_app
8
+ end
9
+
10
+ def rackup(address, port = nil, &block)
11
+ app = Rack::Builder.new(&block).to_app
12
+ mount(app, address, port)
13
+ end
14
+
15
+ def lambda(address, port = nil, &block)
16
+ mount(block, address, port)
17
+ end
18
+
19
+ def sinatra(address, port = nil, &block)
20
+ require "sinatra/base"
21
+ sinatra_app = Class.new(Sinatra::Base)
22
+ sinatra_app.class_eval(&block)
23
+ mount(sinatra_app.new, address, port)
24
+ end
25
+
26
+ def unmount_all
27
+ registry.clear
28
+ end
29
+
30
+ def application_for(address, port = nil)
31
+ port ||= Net::HTTP.default_port
32
+ registry[[address, port]]
33
+ end
34
+
35
+ private
36
+
37
+ def registry
38
+ @registry ||= {}
39
+ end
40
+
41
+ end
42
+
43
+ extend Registry
44
+
45
+ end
data/lib/sham_rack.rb ADDED
@@ -0,0 +1 @@
1
+ require "sham_rack/core_ext/net/http"
@@ -0,0 +1,214 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ require "sham_rack"
4
+ require "open-uri"
5
+ require "restclient"
6
+ require "rack"
7
+
8
+ class PlainTextApp
9
+
10
+ def call(env)
11
+ [
12
+ "200 OK",
13
+ { "Content-Type" => "text/plain", "Content-Length" => message.length.to_s },
14
+ [message]
15
+ ]
16
+ end
17
+
18
+ end
19
+
20
+ class SimpleMessageApp < PlainTextApp
21
+
22
+ def initialize(message)
23
+ @message = message
24
+ end
25
+
26
+ attr_reader :message
27
+
28
+ end
29
+
30
+ class EnvRecordingApp < PlainTextApp
31
+
32
+ def call(env)
33
+ @last_env = env
34
+ super
35
+ end
36
+
37
+ attr_reader :last_env
38
+
39
+ def message
40
+ "env stored for later perusal"
41
+ end
42
+
43
+ end
44
+
45
+ class UpcaseBody
46
+
47
+ def initialize(app)
48
+ @app = app
49
+ end
50
+
51
+ def call(env)
52
+ status, headers, body = @app.call(env)
53
+ upcased_body = Array(body).map { |x| x.upcase }
54
+ [status, headers, upcased_body]
55
+ end
56
+
57
+ end
58
+
59
+ describe ShamRack do
60
+
61
+ after(:each) do
62
+ ShamRack.unmount_all
63
+ end
64
+
65
+ describe "mounted Rack application" do
66
+
67
+ before(:each) do
68
+ ShamRack.mount(SimpleMessageApp.new("Hello, world"), "www.test.xyz")
69
+ end
70
+
71
+ it "can be accessed using Net::HTTP" do
72
+ response = Net::HTTP.start("www.test.xyz") do |http|
73
+ http.request(Net::HTTP::Get.new("/"))
74
+ end
75
+ response.body.should == "Hello, world"
76
+ end
77
+
78
+ it "can be accessed using open-uri" do
79
+ response = open("http://www.test.xyz")
80
+ response.status.should == ["200", "OK"]
81
+ response.read.should == "Hello, world"
82
+ end
83
+
84
+ it "can be accessed using RestClient" do
85
+ response = RestClient.get("http://www.test.xyz")
86
+ response.code.should == 200
87
+ response.to_s.should == "Hello, world"
88
+ end
89
+
90
+ end
91
+
92
+ describe "#rackup" do
93
+
94
+ it "mounts an app created using Rack::Builder" do
95
+
96
+ ShamRack.rackup("rackup.xyz") do
97
+ use UpcaseBody
98
+ run SimpleMessageApp.new("Racked!")
99
+ end
100
+
101
+ open("http://rackup.xyz").read.should == "RACKED!"
102
+
103
+ end
104
+
105
+ end
106
+
107
+ describe "#lambda" do
108
+
109
+ it "mounts associated block as an app" do
110
+
111
+ ShamRack.lambda("simple.xyz") do |env|
112
+ ["200 OK", { "Content-type" => "text/plain" }, "Easy, huh?"]
113
+ end
114
+
115
+ open("http://simple.xyz").read.should == "Easy, huh?"
116
+
117
+ end
118
+
119
+ end
120
+
121
+ describe "#sinatra" do
122
+
123
+ it "mounts associated block as a Sinatra app" do
124
+
125
+ ShamRack.sinatra("sinatra.xyz") do
126
+ get "/hello/:subject" do
127
+ "Hello, #{params[:subject]}"
128
+ end
129
+ end
130
+
131
+ open("http://sinatra.xyz/hello/stranger").read.should == "Hello, stranger"
132
+
133
+ end
134
+
135
+ end
136
+
137
+ describe "Rack environment" do
138
+
139
+ before(:each) do
140
+ @env_recorder = recorder = EnvRecordingApp.new
141
+ ShamRack.rackup("env.xyz") do
142
+ use Rack::Lint
143
+ run recorder
144
+ end
145
+ end
146
+
147
+ def env
148
+ @env_recorder.last_env
149
+ end
150
+
151
+ it "is valid" do
152
+
153
+ open("http://env.xyz/blah?q=abc")
154
+
155
+ env["REQUEST_METHOD"].should == "GET"
156
+ env["SCRIPT_NAME"].should == ""
157
+ env["PATH_INFO"].should == "/blah"
158
+ env["QUERY_STRING"].should == "q=abc"
159
+ env["SERVER_NAME"].should == "env.xyz"
160
+ env["SERVER_PORT"].should == "80"
161
+
162
+ env["rack.version"].should == [0,1]
163
+ env["rack.url_scheme"].should == "http"
164
+
165
+ env["rack.multithread"].should == true
166
+ env["rack.multiprocess"].should == true
167
+ env["rack.run_once"].should == false
168
+
169
+ end
170
+
171
+ it "provides request headers" do
172
+
173
+ Net::HTTP.start("env.xyz") do |http|
174
+ request = Net::HTTP::Get.new("/")
175
+ request["Foo-bar"] = "baz"
176
+ http.request(request)
177
+ end
178
+
179
+ env["HTTP_FOO_BAR"].should == "baz"
180
+
181
+ end
182
+
183
+ it "supports POST" do
184
+
185
+ RestClient.post("http://env.xyz/resource", "q" => "rack")
186
+
187
+ env["REQUEST_METHOD"].should == "POST"
188
+ env["CONTENT_TYPE"].should == "application/x-www-form-urlencoded"
189
+ env["rack.input"].read.should == "q=rack"
190
+
191
+ end
192
+
193
+ it "supports PUT" do
194
+
195
+ RestClient.put("http://env.xyz/thing1", "stuff", :content_type => "text/plain")
196
+
197
+ env["REQUEST_METHOD"].should == "PUT"
198
+ env["CONTENT_TYPE"].should == "text/plain"
199
+ env["rack.input"].read.should == "stuff"
200
+
201
+ end
202
+
203
+ it "supports DELETE" do
204
+
205
+ RestClient.delete("http://env.xyz/thing/1")
206
+
207
+ env["REQUEST_METHOD"].should == "DELETE"
208
+ env["PATH_INFO"].should == "/thing/1"
209
+
210
+ end
211
+
212
+ end
213
+
214
+ end
@@ -0,0 +1,12 @@
1
+ require "rubygems"
2
+ require "spec"
3
+ require "rr"
4
+ require "ruby-debug"
5
+
6
+ project_root = File.expand_path("#{__FILE__}/../..")
7
+ $LOAD_PATH << "#{project_root}/lib"
8
+ $LOAD_PATH << "#{project_root}/spec/support/lib"
9
+
10
+ Spec::Runner.configure do |config|
11
+ config.mock_with RR::Adapters::Rspec
12
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mdub-sham_rack
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mike Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-06 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: ShamRack plumbs Net::HTTP directly into Rack, for quick and easy HTTP testing.
17
+ email: mdub@dogbiscuit.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - README.markdown
26
+ - Rakefile
27
+ - VERSION.yml
28
+ - lib/sham_rack.rb
29
+ - lib/sham_rack/core_ext/net/http.rb
30
+ - lib/sham_rack/http.rb
31
+ - lib/sham_rack/registry.rb
32
+ - spec/sham_rack_spec.rb
33
+ - spec/spec_helper.rb
34
+ has_rdoc: true
35
+ homepage: http://github.com/mdub/sham_rack
36
+ post_install_message:
37
+ rdoc_options:
38
+ - --charset=UTF-8
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project:
56
+ rubygems_version: 1.2.0
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Net::HTTP-to-Rack plumbing
60
+ test_files:
61
+ - spec/sham_rack_spec.rb
62
+ - spec/spec_helper.rb