leadlight 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/Gemfile.lock +5 -7
  2. data/README.md +82 -0
  3. data/default.gems +1 -0
  4. data/leadlight.gemspec +16 -7
  5. data/lib/leadlight.rb +18 -62
  6. data/lib/leadlight/basic_converter.rb +18 -0
  7. data/lib/leadlight/codec.rb +2 -0
  8. data/lib/leadlight/entity.rb +1 -0
  9. data/lib/leadlight/errors.rb +10 -2
  10. data/lib/leadlight/header_helpers.rb +11 -0
  11. data/lib/leadlight/hyperlinkable.rb +1 -1
  12. data/lib/leadlight/representation.rb +19 -1
  13. data/lib/leadlight/request.rb +44 -11
  14. data/lib/leadlight/service.rb +4 -9
  15. data/lib/leadlight/service_class_methods.rb +69 -0
  16. data/lib/leadlight/service_middleware.rb +2 -14
  17. data/lib/leadlight/tint.rb +6 -2
  18. data/lib/leadlight/tint_helper.rb +27 -4
  19. data/lib/leadlight/type_map.rb +101 -0
  20. data/spec/cassettes/Leadlight/authorized_GitHub_example/_user/has_the_expected_content.yml +8 -8
  21. data/spec/cassettes/Leadlight/authorized_GitHub_example/_user/indicates_the_expected_oath_scopes.yml +8 -8
  22. data/spec/cassettes/Leadlight/authorized_GitHub_example/adding_and_removing_team_members.yml +273 -284
  23. data/spec/cassettes/Leadlight/authorized_GitHub_example/{adding_and_removing_team_members/.yml → adding_and_removing_teams.yml} +57 -84
  24. data/spec/cassettes/Leadlight/authorized_GitHub_example/test_team/.yml +111 -117
  25. data/spec/cassettes/Leadlight/basic_GitHub_example/_root/.yml +58 -25
  26. data/spec/cassettes/Leadlight/basic_GitHub_example/_root/__location__/.yml +58 -25
  27. data/spec/cassettes/Leadlight/basic_GitHub_example/_root/should_be_a_204_no_content.yml +58 -25
  28. data/spec/cassettes/Leadlight/tinted_GitHub_example/_root/.yml +58 -25
  29. data/spec/cassettes/Leadlight/tinted_GitHub_example/_root/__location__/.yml +58 -25
  30. data/spec/cassettes/Leadlight/tinted_GitHub_example/_root/should_be_a_204_no_content.yml +58 -25
  31. data/spec/cassettes/Leadlight/tinted_GitHub_example/_user/has_the_expected_content.yml +122 -50
  32. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/.yml +190 -77
  33. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_be_able_to_follow_next_link.yml +260 -104
  34. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_be_enumerable.yml +616 -212
  35. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_be_enumerable_over_page_boundaries.yml +331 -131
  36. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_have_next_and_last_links.yml +190 -77
  37. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_link/exists.yml +58 -25
  38. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_link/links_to_the_expected_URL.yml +58 -25
  39. data/spec/leadlight/hyperlinkable_spec.rb +3 -1
  40. data/spec/leadlight/link_template_spec.rb +2 -1
  41. data/spec/leadlight/request_spec.rb +44 -21
  42. data/spec/leadlight/service_middleware_spec.rb +9 -46
  43. data/spec/leadlight/service_spec.rb +30 -24
  44. data/spec/leadlight/tint_helper_spec.rb +67 -1
  45. data/spec/leadlight/tint_spec.rb +69 -0
  46. data/spec/leadlight/type_map_spec.rb +127 -0
  47. data/spec/leadlight_spec.rb +102 -51
  48. data/spec/support/credentials.rb +10 -2
  49. data/spec/support/vcr.rb +1 -1
  50. metadata +61 -32
  51. data/lib/leadlight/type.rb +0 -71
  52. data/spec/leadlight/type_spec.rb +0 -137
@@ -23,7 +23,9 @@ module Leadlight
23
23
  }
24
24
 
25
25
  before do
26
- representation.stub(__response__: response, __service__: service)
26
+ representation.stub(__response__: response,
27
+ __service__: service,
28
+ __location__: '/foo')
27
29
  end
28
30
 
29
31
  describe '.extend' do
@@ -1,12 +1,13 @@
1
1
  require 'spec_helper_lite'
2
2
  require 'leadlight/link_template'
3
+ require 'leadlight/type_map'
3
4
 
4
5
  module Leadlight
5
6
  describe LinkTemplate do
6
7
 
7
8
  # TODO: This setup is loony. Refactor.
8
9
  subject { LinkTemplate.new(service, href, rel, title, options) }
9
- let(:service) { stub(:service) }
10
+ let(:service) { stub(:service, type_map: TypeMap.new) }
10
11
  let(:request) { stub(:request) }
11
12
  let(:result) { stub(:result) }
12
13
  let(:href) { '/TEST_PATH/{n}/{m}/' }
@@ -6,6 +6,14 @@ module Leadlight
6
6
  describe Request do
7
7
  include Timeout
8
8
 
9
+ before :all do
10
+ @old_abort_on_exception = Thread.abort_on_exception
11
+ Thread.abort_on_exception = true
12
+ end
13
+
14
+ after :all do
15
+ Thread.abort_on_exception = @old_abort_on_exception
16
+ end
9
17
 
10
18
  # The Faraday connection API works like this:
11
19
  #
@@ -29,24 +37,34 @@ module Leadlight
29
37
 
30
38
  def run_completion_handlers(n=1)
31
39
  n.times do
32
- completion_handlers.pop.call(@env)
40
+ handler = completion_handlers.pop
41
+ handler.call(@env)
33
42
  end
34
43
  end
44
+
45
+ def success?
46
+ true
47
+ end
35
48
  end
36
49
 
37
- subject { Request.new(connection, url, http_method, params, body) }
50
+ subject { Request.new(service, connection, url, http_method, params, body) }
51
+ let(:service) { stub(:service, :type_map => type_map) }
52
+ let(:type_map) { stub(:type_map).as_null_object }
38
53
  let(:connection) { stub(:connection, :run_request => faraday_response) }
39
54
  let(:url) { stub(:url) }
40
55
  let(:http_method){ :get }
41
56
  let(:body) { stub(:body) }
42
57
  let(:params) { {} }
43
- let(:faraday_request) {stub(:faraday_request)}
58
+ let(:faraday_request) {stub(:faraday_request, options: {})}
44
59
  let(:on_complete_handlers) { [] }
45
- let(:faraday_response) { FakeFaradayResponse.new(faraday_env) }
46
- let(:faraday_env) { {:leadlight_representation => representation} }
60
+ let(:faraday_env) { {} }
47
61
  let(:representation) { stub(:representation) }
62
+ let(:faraday_response) { FakeFaradayResponse.new(faraday_env) }
48
63
 
49
64
  def run_completion_handlers
65
+ faraday_env[:status] ||= 200
66
+ faraday_env[:response] ||= faraday_response
67
+ faraday_env[:leadlight_representation] ||= representation
50
68
  faraday_response.run_completion_handlers
51
69
  end
52
70
 
@@ -55,7 +73,11 @@ module Leadlight
55
73
  do_it(&block)
56
74
  end
57
75
  run_completion_handlers
58
- t.join.value
76
+ t.join(1).value
77
+ end
78
+
79
+ before do
80
+ subject.stub!(:represent => representation)
59
81
  end
60
82
 
61
83
  context "for GET" do
@@ -74,17 +96,17 @@ module Leadlight
74
96
  describe "#submit" do
75
97
  it "starts a request runnning" do
76
98
  connection.should_receive(:run_request).
77
- with(http_method, url, body, {}).
99
+ with(http_method, url, anything, {}).
78
100
  and_return(faraday_response)
79
101
  subject.submit
80
102
  end
81
103
 
82
104
  it "triggers the on_prepare_request hook in the block passed to #run_request" do
83
105
  yielded = :nothing
84
- faraday_request = stub
106
+ faraday_request = stub(options: {}, headers: {})
85
107
  connection.stub(:run_request).
86
108
  and_yield(faraday_request).
87
- and_return(faraday_response)
109
+ and_return(stub.as_null_object)
88
110
  subject.on_prepare_request do |request|
89
111
  yielded = request
90
112
  end
@@ -103,8 +125,8 @@ module Leadlight
103
125
  trace << "wait finished"
104
126
  end
105
127
  trace << "completing request"
106
- faraday_response.run_completion_handlers
107
- thread.join
128
+ run_completion_handlers
129
+ thread.join(1)
108
130
  trace << "request completed"
109
131
  trace.pop.should eq("completing request")
110
132
  trace.pop.should eq("wait finished")
@@ -125,8 +147,8 @@ module Leadlight
125
147
  trace << "submit"
126
148
  subject.submit
127
149
  trace << "completing request"
128
- faraday_response.run_completion_handlers
129
- thread.join
150
+ run_completion_handlers
151
+ thread.join(1)
130
152
  trace << "request completed"
131
153
  trace.pop.should eq("submit")
132
154
  trace.pop.should eq("completing request")
@@ -181,7 +203,7 @@ module Leadlight
181
203
  subject.wait
182
204
  end
183
205
  run_completion_handlers
184
- t.join
206
+ t.join(1)
185
207
  end
186
208
 
187
209
  it "queues hooks to be run on completion" do
@@ -197,8 +219,6 @@ module Leadlight
197
219
  end
198
220
 
199
221
  it "calls hooks with the faraday response" do
200
- Faraday::Response.should_receive(:new).with(faraday_env).
201
- and_return(faraday_response)
202
222
  yielded = :nothing
203
223
  subject.on_complete do |response|
204
224
  yielded = response
@@ -216,21 +236,24 @@ module Leadlight
216
236
  subject.wait
217
237
  end
218
238
  run_completion_handlers
219
- t.join
239
+ t.join(1)
220
240
  subject
221
241
  end
222
242
 
223
243
  it "raises an error when the response is a client error" do
224
- faraday_env[:status] = 404
225
- expect { submit_and_complete.raise_on_error }.to raise_error(ResourceNotFound)
244
+ faraday_response.should_receive(:success?).and_return(false)
245
+ subject.should_receive(:raise).with(representation)
246
+ submit_and_complete.raise_on_error
226
247
  end
227
248
 
228
249
  it "raises after completion when called before completion" do
229
- faraday_env[:status] = 500
250
+ faraday_response.should_receive(:success?).and_return(false)
230
251
  subject.raise_on_error
231
- expect { submit_and_complete }.to raise_error(ServerError)
252
+ subject.should_receive(:raise).with(representation)
253
+ submit_and_complete
232
254
  end
233
255
 
234
256
  end
257
+
235
258
  end
236
259
  end
@@ -17,65 +17,28 @@ module Leadlight
17
17
  let(:app) { ->(env){stub(on_complete: nil)} }
18
18
  let(:response) { [200, {}, ''] }
19
19
  let(:result_env) {
20
- test_stack.get('/').env
20
+ do_get('/').env
21
21
  }
22
22
  let(:representation) { result_env[:leadlight_representation] }
23
+ let(:leadlight_request) { stub(:leadlight_request, represent: stub) }
24
+
25
+ def do_get(path)
26
+ test_stack.get(path) do |request|
27
+ request.options[:leadlight_request] = leadlight_request
28
+ end
29
+ end
23
30
 
24
31
  before do
25
32
  faraday_stubs.get('/') {response}
26
33
  end
27
34
 
28
35
  it 'adds :leadlight_service to env before app' do
29
- test_stack.get('/').env[:leadlight_service].should equal(service)
30
- end
31
-
32
- it 'extends the representation with Representation' do
33
- representation.should be_a(Representation)
34
- end
35
-
36
- it 'makes the representation hyperlinkable' do
37
- representation.should be_a(Hyperlinkable)
36
+ do_get('/').env[:leadlight_service].should equal(service)
38
37
  end
39
38
 
40
39
  it 'requests JSON, then YAML, then XML, then HTML' do
41
40
  result_env[:request_headers]['Accept'].
42
41
  should eq('application/json, text/x-yaml, application/xml, application/xhtml+xml, text/html, text/plain')
43
42
  end
44
-
45
- context 'with a no-content response' do
46
- let(:response) { [204, {}, ''] }
47
- let(:result_env) {
48
- test_stack.get('/').env
49
- }
50
-
51
- it 'sets :leadlight_representation to a Blank' do
52
- result_env[:leadlight_representation].should be_a(Blank)
53
- end
54
- end
55
-
56
- context 'with a blank JSON response' do
57
- let(:response) {
58
- [200, {'Content-Type' => 'application/json', 'Content-Length' => '0'}, '']
59
- }
60
- let(:result_env) {
61
- test_stack.get('/').env
62
- }
63
-
64
- it 'sets :leadlight_representation to a Blank' do
65
- result_env[:leadlight_representation].should be_a(Blank)
66
- end
67
- end
68
-
69
- context 'with a non-blank JSON response' do
70
- let(:response) {
71
- [200, {'Content-Type' => 'application/json', 'Content-Length' => '7'}, '[1,2,3]']
72
- }
73
- let(:result_env) {
74
- test_stack.get('/').env
75
- }
76
- it 'sets :leadlight_representation to the result of parsing the JSON' do
77
- result_env[:leadlight_representation].should eq([1,2,3])
78
- end
79
- end
80
43
  end
81
44
  end
@@ -4,7 +4,14 @@ require 'leadlight/service'
4
4
  module Leadlight
5
5
  describe Service do
6
6
  subject { klass.new(service_options) }
7
- let(:klass) { Class.new do include Service end }
7
+ let(:klass) {
8
+ Class.new do
9
+ include Service
10
+
11
+ def execute_hook(*)
12
+ end
13
+ end
14
+ }
8
15
  let(:connection) { stub(:connection, get: response) }
9
16
  let(:representation) { stub(:representation) }
10
17
  let(:response) { stub(:response, env: env) }
@@ -12,66 +19,65 @@ module Leadlight
12
19
  let(:service_options) { {codec: codec} }
13
20
  let(:codec) { stub(:codec) }
14
21
  let(:request) { stub(:request).as_null_object }
22
+ let(:request_class) { stub(:request_class, new: request) }
15
23
 
16
24
  before do
17
- subject.stub(connection: connection, url: nil)
25
+ subject.stub(connection: connection,
26
+ url: nil,
27
+ request_class: request_class)
18
28
  end
19
29
 
20
30
  shared_examples_for "an HTTP client" do |http_method|
21
31
  describe "##{http_method}" do
22
- before do
23
- Request.stub(:new).and_return(request)
24
- end
25
-
26
32
  it 'returns a new request object' do
27
- Request.should_receive(:new).and_return(request)
33
+ request_class.should_receive(:new).and_return(request)
28
34
  subject.public_send(http_method, '/').should equal(request)
29
35
  end
30
36
 
37
+ it 'passes self to the request' do
38
+ request_class.should_receive(:new).
39
+ with(subject, anything, anything, anything, anything, anything).
40
+ and_return(request)
41
+ subject.public_send(http_method, '/somepath')
42
+ end
43
+
31
44
  it 'passes the connection to the request' do
32
- Request.should_receive(:new).
33
- with(connection, anything, anything, anything, anything).
45
+ request_class.should_receive(:new).
46
+ with(anything, connection, anything, anything, anything, anything).
34
47
  and_return(request)
35
48
  subject.public_send(http_method, '/somepath')
36
49
  end
37
50
 
38
51
  it 'passes the path to the request' do
39
- Request.should_receive(:new).
40
- with(anything, '/somepath', anything, anything, anything).
52
+ request_class.should_receive(:new).
53
+ with(anything, anything, '/somepath', anything, anything, anything).
41
54
  and_return(request)
42
55
  subject.public_send(http_method, '/somepath')
43
56
  end
44
57
 
45
58
  it 'passes the method to the request' do
46
- Request.should_receive(:new).
47
- with(anything, anything, http_method, anything, anything).
59
+ request_class.should_receive(:new).
60
+ with(anything, anything, anything, http_method, anything, anything).
48
61
  and_return(request)
49
62
  subject.public_send(http_method, '/somepath')
50
63
  end
51
64
 
52
65
  it 'passes the params to the request' do
53
66
  params = stub
54
- Request.should_receive(:new).
55
- with(anything, anything, anything, params, anything).
67
+ request_class.should_receive(:new).
68
+ with(anything, anything, anything, anything, params, anything).
56
69
  and_return(request)
57
70
  subject.public_send(http_method, '/somepath', params)
58
71
  end
59
72
 
60
73
  it 'passes the body to the request' do
61
74
  body = stub
62
- Request.should_receive(:new).
63
- with(anything, anything, anything, anything, body).
75
+ request_class.should_receive(:new).
76
+ with(anything, anything, anything, anything, anything, body).
64
77
  and_return(request)
65
78
  subject.public_send(http_method, '/somepath', {}, body)
66
79
  end
67
80
 
68
- it 'adds a prepare_request callback' do
69
- faraday_request = stub(:faraday_request)
70
- request.stub(:on_prepare_request).and_yield(faraday_request)
71
- subject.should_receive(:prepare_request).with(faraday_request)
72
- subject.public_send(http_method, '/')
73
- end
74
-
75
81
  context 'given a block' do
76
82
  define_method(:do_it) do
77
83
  subject.public_send(http_method, '/') do |yielded|
@@ -10,7 +10,8 @@ module Leadlight
10
10
  }
11
11
  let(:response) {
12
12
  stub(:response,
13
- env: env)
13
+ env: env,
14
+ status: 200)
14
15
  }
15
16
  let(:env) { {response_headers: headers} }
16
17
  let(:headers) {
@@ -75,7 +76,44 @@ module Leadlight
75
76
  end
76
77
  end
77
78
 
79
+ describe '#match_status' do
80
+ it 'allows execution to proceed on match' do
81
+ object.should_receive(:baz)
82
+ subject.exec_tint do
83
+ match_status(200)
84
+ baz
85
+ end
86
+ end
87
+
88
+ it 'can match on a range' do
89
+ object.should_receive(:baz)
90
+ subject.exec_tint do
91
+ match_status(200..299)
92
+ baz
93
+ end
94
+ end
95
+
96
+ it 'does not allow execution to proceed on no match' do
97
+ object.should_not_receive(:baz)
98
+ subject.exec_tint do
99
+ match_status(301)
100
+ baz
101
+ end
102
+ end
103
+ end
104
+
78
105
  describe '#match' do
106
+ let(:successful_matcher) {
107
+ stub.tap do |matcher|
108
+ matcher.should_receive(:===).with(object).and_return(true)
109
+ end
110
+ }
111
+ let(:failing_matcher) {
112
+ stub.tap do |matcher|
113
+ matcher.should_receive(:===).with(object).and_return(false)
114
+ end
115
+ }
116
+
79
117
  it 'allows execution to proceed on true return' do
80
118
  object.should_receive(:baz)
81
119
  subject.exec_tint do
@@ -84,6 +122,34 @@ module Leadlight
84
122
  end
85
123
  end
86
124
 
125
+ it 'can match a given param against the representation' do
126
+ matcher = successful_matcher
127
+ object.should_receive(:baz)
128
+ subject.exec_tint do
129
+ match matcher
130
+ baz
131
+ end
132
+ end
133
+
134
+ it 'can match several params against the representation' do
135
+ matchers = [ failing_matcher, successful_matcher ]
136
+ object.should_receive(:baz)
137
+ subject.exec_tint do
138
+ match *matchers
139
+ baz
140
+ end
141
+ end
142
+
143
+ it 'can match using params and a block' do
144
+ param_matcher = failing_matcher
145
+ block_matcher = proc { (2 + 2) == 4 }
146
+ object.should_receive(:baz)
147
+ subject.exec_tint do
148
+ match param_matcher, &block_matcher
149
+ baz
150
+ end
151
+ end
152
+
87
153
  it 'does not allow execution to proceed on false return' do
88
154
  object.should_not_receive(:baz)
89
155
  subject.exec_tint do