alice 0.1.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/.document +5 -0
- data/.gitignore +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +76 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/alice.rb +76 -0
- data/lib/alice/adapter/net_http.rb +30 -0
- data/lib/alice/adapter/patron.rb +35 -0
- data/lib/alice/adapter/test.rb +94 -0
- data/lib/alice/adapter/typhoeus.rb +61 -0
- data/lib/alice/builder.rb +39 -0
- data/lib/alice/connection.rb +183 -0
- data/lib/alice/error.rb +6 -0
- data/lib/alice/middleware.rb +54 -0
- data/lib/alice/request.rb +77 -0
- data/lib/alice/request/active_support_json.rb +20 -0
- data/lib/alice/request/yajl.rb +18 -0
- data/lib/alice/response.rb +48 -0
- data/lib/alice/response/active_support_json.rb +22 -0
- data/lib/alice/response/yajl.rb +20 -0
- data/test/adapters/live_test.rb +157 -0
- data/test/adapters/test_middleware_test.rb +28 -0
- data/test/adapters/typhoeus_test.rb +28 -0
- data/test/connection_app_test.rb +52 -0
- data/test/connection_test.rb +167 -0
- data/test/env_test.rb +35 -0
- data/test/helper.rb +25 -0
- data/test/live_server.rb +34 -0
- data/test/request_middleware_test.rb +19 -0
- data/test/response_middleware_test.rb +19 -0
- metadata +114 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
module Alice
|
2
|
+
class Response::ActiveSupportJson < Response::Middleware
|
3
|
+
begin
|
4
|
+
if !defined?(ActiveSupport::JSON)
|
5
|
+
require 'active_support'
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.register_on_complete(env)
|
9
|
+
env[:response].on_complete do |finished_env|
|
10
|
+
finished_env[:body] = ActiveSupport::JSON.decode(finished_env[:body])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
rescue LoadError => e
|
14
|
+
self.load_error = e
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(app)
|
18
|
+
super
|
19
|
+
@parser = nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Alice
|
2
|
+
class Response::Yajl < Response::Middleware
|
3
|
+
begin
|
4
|
+
require 'yajl'
|
5
|
+
|
6
|
+
def self.register_on_complete(env)
|
7
|
+
env[:response].on_complete do |finished_env|
|
8
|
+
finished_env[:body] = Yajl::Parser.parse(finished_env[:body])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
rescue LoadError => e
|
12
|
+
self.load_error = e
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(app)
|
16
|
+
super
|
17
|
+
@parser = nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'helper'))
|
2
|
+
|
3
|
+
if Alice::TestCase::LIVE_SERVER
|
4
|
+
module Adapters
|
5
|
+
class LiveTest < Alice::TestCase
|
6
|
+
Alice::Adapter.all_loaded_constants.each do |adapter|
|
7
|
+
describe "with #{adapter} adapter" do
|
8
|
+
before do
|
9
|
+
@connection = Alice::Connection.new LIVE_SERVER do
|
10
|
+
use adapter
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#get" do
|
15
|
+
it "raises on 404" do
|
16
|
+
assert_raise(Alice::Error::ResourceNotFound) { @connection.get('/nothing') }
|
17
|
+
end
|
18
|
+
|
19
|
+
it "retrieves the response body" do
|
20
|
+
assert_equal 'hello world', @connection.get('hello_world').body
|
21
|
+
end
|
22
|
+
|
23
|
+
it "send url-encoded params" do
|
24
|
+
resp = @connection.get do |req|
|
25
|
+
req.url 'hello', 'name' => 'zack'
|
26
|
+
end
|
27
|
+
assert_equal('hello zack', resp.body)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "retrieves the response headers" do
|
31
|
+
assert_equal 'text/html', @connection.get('hello_world').headers['content-type']
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#post" do
|
36
|
+
it "raises on 404" do
|
37
|
+
assert_raise(Alice::Error::ResourceNotFound) { @connection.post('/nothing') }
|
38
|
+
end
|
39
|
+
|
40
|
+
it "send url-encoded params" do
|
41
|
+
resp = @connection.post do |req|
|
42
|
+
req.url 'echo_name'
|
43
|
+
req.body = {'name' => 'zack'}
|
44
|
+
end
|
45
|
+
assert_equal %("zack"), resp.body
|
46
|
+
end
|
47
|
+
|
48
|
+
it "send url-encoded nested params" do
|
49
|
+
resp = @connection.post do |req|
|
50
|
+
req.url 'echo_name'
|
51
|
+
req.body = {'name' => {'first' => 'zack'}}
|
52
|
+
end
|
53
|
+
assert_equal %({"first"=>"zack"}), resp.body
|
54
|
+
end
|
55
|
+
|
56
|
+
it "retrieves the response headers" do
|
57
|
+
assert_equal 'text/html', @connection.post('echo_name').headers['content-type']
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# http://github.com/toland/patron/issues/#issue/9
|
62
|
+
if ENV['FORCE'] || adapter != Alice::Adapter::Patron
|
63
|
+
describe "#put" do
|
64
|
+
it "raises on 404" do
|
65
|
+
assert_raise(Alice::Error::ResourceNotFound) { @connection.put('/nothing') }
|
66
|
+
end
|
67
|
+
|
68
|
+
it "send url-encoded params" do
|
69
|
+
resp = @connection.put do |req|
|
70
|
+
req.url 'echo_name'
|
71
|
+
req.body = {'name' => 'zack'}
|
72
|
+
end
|
73
|
+
assert_equal %("zack"), resp.body
|
74
|
+
end
|
75
|
+
|
76
|
+
it "send url-encoded nested params" do
|
77
|
+
resp = @connection.put do |req|
|
78
|
+
req.url 'echo_name'
|
79
|
+
req.body = {'name' => {'first' => 'zack'}}
|
80
|
+
end
|
81
|
+
assert_equal %({"first"=>"zack"}), resp.body
|
82
|
+
end
|
83
|
+
|
84
|
+
it "retrieves the response headers" do
|
85
|
+
assert_equal 'text/html', @connection.put('echo_name').headers['content-type']
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# http://github.com/pauldix/typhoeus/issues#issue/7
|
91
|
+
if ENV['FORCE'] || adapter != Alice::Adapter::Typhoeus
|
92
|
+
describe "#head" do
|
93
|
+
it "raises on 404" do
|
94
|
+
assert_raise(Alice::Error::ResourceNotFound) { @connection.head('/nothing') }
|
95
|
+
end
|
96
|
+
|
97
|
+
it "send url-encoded params" do
|
98
|
+
resp = @connection.head do |req|
|
99
|
+
req.url 'hello', 'name' => 'zack'
|
100
|
+
end
|
101
|
+
assert_equal 'text/html', resp.headers['content-type']
|
102
|
+
end
|
103
|
+
|
104
|
+
it "retrieves no response body" do
|
105
|
+
assert_equal '', @connection.head('hello_world').body.to_s
|
106
|
+
end
|
107
|
+
|
108
|
+
it "retrieves the response headers" do
|
109
|
+
assert_equal 'text/html', @connection.head('hello_world').headers['content-type']
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "#delete" do
|
115
|
+
it "raises on 404" do
|
116
|
+
assert_raise(Alice::Error::ResourceNotFound) { @connection.delete('/nothing') }
|
117
|
+
end
|
118
|
+
|
119
|
+
it "retrieves the response headers" do
|
120
|
+
assert_equal 'text/html', @connection.delete('delete_with_json').headers['content-type']
|
121
|
+
end
|
122
|
+
|
123
|
+
it "retrieves the body" do
|
124
|
+
assert_match /deleted/, @connection.delete('delete_with_json').body
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "async requests" do
|
129
|
+
it "clears parallel manager after running a single request" do
|
130
|
+
assert !@connection.in_parallel?
|
131
|
+
resp = @connection.get('hello_world')
|
132
|
+
assert !@connection.in_parallel?
|
133
|
+
assert_equal 'hello world', @connection.get('hello_world').body
|
134
|
+
end
|
135
|
+
|
136
|
+
it "uses parallel manager to run multiple json requests" do
|
137
|
+
resp1, resp2 = nil, nil
|
138
|
+
|
139
|
+
@connection.in_parallel(adapter.setup_parallel_manager) do
|
140
|
+
resp1 = @connection.get('json')
|
141
|
+
resp2 = @connection.get('json')
|
142
|
+
if adapter.supports_parallel_requests?
|
143
|
+
assert @connection.in_parallel?
|
144
|
+
assert_nil resp1.body
|
145
|
+
assert_nil resp2.body
|
146
|
+
end
|
147
|
+
end
|
148
|
+
assert !@connection.in_parallel?
|
149
|
+
assert_equal '[1,2,3]', resp1.body
|
150
|
+
assert_equal '[1,2,3]', resp2.body
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'helper'))
|
2
|
+
|
3
|
+
module Adapters
|
4
|
+
class TestMiddleware < Alice::TestCase
|
5
|
+
describe "Test Middleware with simple path" do
|
6
|
+
before :all do
|
7
|
+
@conn = Alice::Connection.new do
|
8
|
+
adapter :test do |stub|
|
9
|
+
stub.get('/hello') { [200, {'Content-Type' => 'text/html'}, 'hello'] }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
@resp = @conn.get('/hello')
|
13
|
+
end
|
14
|
+
|
15
|
+
it "sets status" do
|
16
|
+
assert_equal 200, @resp.status
|
17
|
+
end
|
18
|
+
|
19
|
+
it "sets headers" do
|
20
|
+
assert_equal 'text/html', @resp.headers['Content-Type']
|
21
|
+
end
|
22
|
+
|
23
|
+
it "sets body" do
|
24
|
+
assert_equal 'hello', @resp.body
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'helper'))
|
2
|
+
|
3
|
+
if Alice::Adapter::Typhoeus.loaded?
|
4
|
+
module Adapters
|
5
|
+
class TestTyphoeus < Alice::TestCase
|
6
|
+
describe "#parse_response_headers" do
|
7
|
+
before do
|
8
|
+
@adapter = Alice::Adapter::Typhoeus.new
|
9
|
+
end
|
10
|
+
|
11
|
+
it "leaves http status line out" do
|
12
|
+
headers = @adapter.parse_response_headers("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
|
13
|
+
assert_equal %w(content-type), headers.keys
|
14
|
+
end
|
15
|
+
|
16
|
+
it "parses lower-cased header name and value" do
|
17
|
+
headers = @adapter.parse_response_headers("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
|
18
|
+
assert_equal 'text/html', headers['content-type']
|
19
|
+
end
|
20
|
+
|
21
|
+
it "parses lower-cased header name and value with colon" do
|
22
|
+
headers = @adapter.parse_response_headers("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nLocation: http://sushi.com/\r\n\r\n")
|
23
|
+
assert_equal 'http://sushi.com/', headers['location']
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
|
3
|
+
class TestConnectionApps < Alice::TestCase
|
4
|
+
class TestAdapter
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
[200, {}, env[:test]]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class TestMiddleWare
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
env[:test] = 'hi'
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
before do
|
26
|
+
@conn = Alice::Connection.new do
|
27
|
+
use TestMiddleWare
|
28
|
+
use TestAdapter
|
29
|
+
end
|
30
|
+
class << @conn.builder
|
31
|
+
attr_reader :ins
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#builder" do
|
36
|
+
it "is built from Alice::Connection constructor" do
|
37
|
+
assert_kind_of Rack::Builder, @conn.builder
|
38
|
+
assert_equal 3, @conn.builder.ins.size
|
39
|
+
end
|
40
|
+
|
41
|
+
it "adds middleware to the Builder stack" do
|
42
|
+
assert_kind_of TestMiddleWare, @conn.builder.ins[0].call(nil)
|
43
|
+
assert_kind_of TestAdapter, @conn.builder.ins[1].call(nil)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#to_app" do
|
48
|
+
it "returns rack-compatible object" do
|
49
|
+
assert @conn.to_app.respond_to?(:call)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
|
3
|
+
class TestConnection < Alice::TestCase
|
4
|
+
describe "#initialize" do
|
5
|
+
it "parses @host out of given url" do
|
6
|
+
conn = Alice::Connection.new "http://sushi.com"
|
7
|
+
assert_equal 'sushi.com', conn.host
|
8
|
+
end
|
9
|
+
|
10
|
+
it "parses nil @port out of given url" do
|
11
|
+
conn = Alice::Connection.new "http://sushi.com"
|
12
|
+
assert_nil conn.port
|
13
|
+
end
|
14
|
+
|
15
|
+
it "parses @scheme out of given url" do
|
16
|
+
conn = Alice::Connection.new "http://sushi.com"
|
17
|
+
assert_equal 'http', conn.scheme
|
18
|
+
end
|
19
|
+
|
20
|
+
it "parses @port out of given url" do
|
21
|
+
conn = Alice::Connection.new "http://sushi.com:815"
|
22
|
+
assert_equal 815, conn.port
|
23
|
+
end
|
24
|
+
|
25
|
+
it "parses nil @path_prefix out of given url" do
|
26
|
+
conn = Alice::Connection.new "http://sushi.com"
|
27
|
+
assert_equal '/', conn.path_prefix
|
28
|
+
end
|
29
|
+
|
30
|
+
it "parses @path_prefix out of given url" do
|
31
|
+
conn = Alice::Connection.new "http://sushi.com/fish"
|
32
|
+
assert_equal '/fish', conn.path_prefix
|
33
|
+
end
|
34
|
+
|
35
|
+
it "parses @path_prefix out of given url option" do
|
36
|
+
conn = Alice::Connection.new :url => "http://sushi.com/fish"
|
37
|
+
assert_equal '/fish', conn.path_prefix
|
38
|
+
end
|
39
|
+
|
40
|
+
it "stores default params from options" do
|
41
|
+
conn = Alice::Connection.new :params => {:a => 1}
|
42
|
+
assert_equal 1, conn.params['a']
|
43
|
+
end
|
44
|
+
|
45
|
+
it "stores default params from uri" do
|
46
|
+
conn = Alice::Connection.new "http://sushi.com/fish?a=1", :params => {'b' => '2'}
|
47
|
+
assert_equal '1', conn.params['a']
|
48
|
+
assert_equal '2', conn.params['b']
|
49
|
+
end
|
50
|
+
|
51
|
+
it "stores default headers from options" do
|
52
|
+
conn = Alice::Connection.new :headers => {:a => 1}
|
53
|
+
assert_equal '1', conn.headers['A']
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#build_url" do
|
58
|
+
it "uses Connection#host as default URI host" do
|
59
|
+
conn = Alice::Connection.new
|
60
|
+
conn.host = 'sushi.com'
|
61
|
+
uri = conn.build_url("/sake.html")
|
62
|
+
assert_equal 'sushi.com', uri.host
|
63
|
+
end
|
64
|
+
|
65
|
+
it "uses Connection#port as default URI port" do
|
66
|
+
conn = Alice::Connection.new
|
67
|
+
conn.port = 23
|
68
|
+
uri = conn.build_url("http://sushi.com")
|
69
|
+
assert_equal 23, uri.port
|
70
|
+
end
|
71
|
+
|
72
|
+
it "uses Connection#scheme as default URI scheme" do
|
73
|
+
conn = Alice::Connection.new 'http://sushi.com'
|
74
|
+
uri = conn.build_url("/sake.html")
|
75
|
+
assert_equal 'http', uri.scheme
|
76
|
+
end
|
77
|
+
|
78
|
+
it "uses Connection#path_prefix to customize the path" do
|
79
|
+
conn = Alice::Connection.new
|
80
|
+
conn.path_prefix = '/fish'
|
81
|
+
uri = conn.build_url("sake.html")
|
82
|
+
assert_equal '/fish/sake.html', uri.path
|
83
|
+
end
|
84
|
+
|
85
|
+
it "uses '/' Connection#path_prefix to customize the path" do
|
86
|
+
conn = Alice::Connection.new
|
87
|
+
conn.path_prefix = '/'
|
88
|
+
uri = conn.build_url("sake.html")
|
89
|
+
assert_equal '/sake.html', uri.path
|
90
|
+
end
|
91
|
+
|
92
|
+
it "forces Connection#path_prefix to be absolute" do
|
93
|
+
conn = Alice::Connection.new
|
94
|
+
conn.path_prefix = 'fish'
|
95
|
+
uri = conn.build_url("sake.html")
|
96
|
+
assert_equal '/fish/sake.html', uri.path
|
97
|
+
end
|
98
|
+
|
99
|
+
it "ignores Connection#path_prefix trailing slash" do
|
100
|
+
conn = Alice::Connection.new
|
101
|
+
conn.path_prefix = '/fish/'
|
102
|
+
uri = conn.build_url("sake.html")
|
103
|
+
assert_equal '/fish/sake.html', uri.path
|
104
|
+
end
|
105
|
+
|
106
|
+
it "allows absolute URI to ignore Connection#path_prefix" do
|
107
|
+
conn = Alice::Connection.new
|
108
|
+
conn.path_prefix = '/fish'
|
109
|
+
uri = conn.build_url("/sake.html")
|
110
|
+
assert_equal '/sake.html', uri.path
|
111
|
+
end
|
112
|
+
|
113
|
+
it "parses url/params into #path" do
|
114
|
+
conn = Alice::Connection.new
|
115
|
+
uri = conn.build_url("http://sushi.com/sake.html")
|
116
|
+
assert_equal '/sake.html', uri.path
|
117
|
+
end
|
118
|
+
|
119
|
+
it "parses url/params into #query" do
|
120
|
+
conn = Alice::Connection.new
|
121
|
+
uri = conn.build_url("http://sushi.com/sake.html", 'a[b]' => '1 + 2')
|
122
|
+
assert_equal "a%5Bb%5D=1%20%2B%202", uri.query
|
123
|
+
end
|
124
|
+
|
125
|
+
it "mashes default params and given params together" do
|
126
|
+
conn = Alice::Connection.new 'http://sushi.com/api?token=abc', :params => {'format' => 'json'}
|
127
|
+
url = conn.build_url("nigiri?page=1", :limit => 5)
|
128
|
+
assert_match /limit=5/, url.query
|
129
|
+
assert_match /page=1/, url.query
|
130
|
+
assert_match /format=json/, url.query
|
131
|
+
assert_match /token=abc/, url.query
|
132
|
+
end
|
133
|
+
|
134
|
+
it "overrides default params with given params" do
|
135
|
+
conn = Alice::Connection.new 'http://sushi.com/api?token=abc', :params => {'format' => 'json'}
|
136
|
+
url = conn.build_url("nigiri?page=1", :limit => 5, :token => 'def', :format => 'xml')
|
137
|
+
assert_match /limit=5/, url.query
|
138
|
+
assert_match /page=1/, url.query
|
139
|
+
assert_match /format=xml/, url.query
|
140
|
+
assert_match /token=def/, url.query
|
141
|
+
assert_no_match /format=json/, url.query
|
142
|
+
assert_no_match /token=abc/, url.query
|
143
|
+
end
|
144
|
+
|
145
|
+
it "parses url into #host" do
|
146
|
+
conn = Alice::Connection.new
|
147
|
+
uri = conn.build_url("http://sushi.com/sake.html")
|
148
|
+
assert_equal "sushi.com", uri.host
|
149
|
+
end
|
150
|
+
|
151
|
+
it "parses url into #port" do
|
152
|
+
conn = Alice::Connection.new
|
153
|
+
uri = conn.build_url("http://sushi.com/sake.html")
|
154
|
+
assert_nil uri.port
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "#params_to_query" do
|
159
|
+
it "converts hash of params to URI-escaped query string" do
|
160
|
+
conn = Alice::Connection.new
|
161
|
+
class << conn
|
162
|
+
public :build_query
|
163
|
+
end
|
164
|
+
assert_equal "a%5Bb%5D=1%20%2B%202", conn.build_query('a[b]' => '1 + 2')
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|