leadlight 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/Gemfile.lock +20 -18
  2. data/default.gems +1 -1
  3. data/leadlight.gemspec +24 -4
  4. data/lib/leadlight.rb +4 -5
  5. data/lib/leadlight/connection_builder.rb +29 -0
  6. data/lib/leadlight/errors.rb +11 -5
  7. data/lib/leadlight/hyperlinkable.rb +31 -10
  8. data/lib/leadlight/lib_ext.rb +3 -0
  9. data/lib/leadlight/lib_ext/faraday/README +6 -0
  10. data/lib/leadlight/lib_ext/faraday/adapter.rb +7 -0
  11. data/lib/leadlight/lib_ext/faraday/builder.rb +47 -0
  12. data/lib/leadlight/lib_ext/faraday/connection.rb +40 -0
  13. data/lib/leadlight/lib_ext/faraday/middleware.rb +7 -0
  14. data/lib/leadlight/link.rb +91 -4
  15. data/lib/leadlight/link_template.rb +13 -5
  16. data/lib/leadlight/null_link.rb +21 -0
  17. data/lib/leadlight/param_hash.rb +42 -0
  18. data/lib/leadlight/representation.rb +23 -3
  19. data/lib/leadlight/request.rb +42 -10
  20. data/lib/leadlight/service.rb +11 -9
  21. data/lib/leadlight/service_class_methods.rb +11 -3
  22. data/lib/leadlight/tint_helper.rb +32 -2
  23. data/spec/cassettes/Leadlight/authorized_GitHub_example/_user/has_the_expected_content.yml +72 -54
  24. data/spec/cassettes/Leadlight/authorized_GitHub_example/_user/indicates_the_expected_oath_scopes.yml +73 -55
  25. data/spec/cassettes/Leadlight/authorized_GitHub_example/adding_and_removing_team_members.yml +353 -265
  26. data/spec/cassettes/Leadlight/authorized_GitHub_example/adding_and_removing_teams.yml +75 -175
  27. data/spec/cassettes/Leadlight/authorized_GitHub_example/team_list/should_have_a_link_back_to_the_org.yml +196 -0
  28. data/spec/cassettes/Leadlight/authorized_GitHub_example/test_team/.yml +152 -108
  29. data/spec/cassettes/Leadlight/authorized_GitHub_example/test_team/has_a_root_link.yml +197 -0
  30. data/spec/cassettes/Leadlight/basic_GitHub_example/_root/.yml +15 -43
  31. data/spec/cassettes/Leadlight/basic_GitHub_example/_root/__location__/.yml +15 -43
  32. data/spec/cassettes/Leadlight/basic_GitHub_example/_root/should_be_a_204_no_content.yml +15 -43
  33. data/spec/cassettes/Leadlight/tinted_GitHub_example/_root/.yml +15 -43
  34. data/spec/cassettes/Leadlight/tinted_GitHub_example/_root/__location__/.yml +15 -43
  35. data/spec/cassettes/Leadlight/tinted_GitHub_example/_root/should_be_a_204_no_content.yml +15 -43
  36. data/spec/cassettes/Leadlight/tinted_GitHub_example/_user/has_the_expected_content.yml +35 -90
  37. data/spec/cassettes/Leadlight/tinted_GitHub_example/bad_links/enables_custom_error_matching.yml +25 -17
  38. data/spec/cassettes/Leadlight/tinted_GitHub_example/bad_links/should_raise_ResourceNotFound.yml +26 -90
  39. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/.yml +57 -140
  40. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_be_able_to_follow_next_link.yml +79 -192
  41. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_be_enumerable.yml +279 -350
  42. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_be_enumerable_over_page_boundaries.yml +98 -243
  43. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_have_next_and_last_links.yml +59 -142
  44. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_link/exists.yml +15 -43
  45. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_link/links_to_the_expected_URL.yml +15 -43
  46. data/spec/leadlight/hyperlinkable_spec.rb +50 -4
  47. data/spec/leadlight/link_spec.rb +70 -10
  48. data/spec/leadlight/link_template_spec.rb +20 -4
  49. data/spec/leadlight/null_link_spec.rb +14 -0
  50. data/spec/leadlight/param_hash_spec.rb +26 -0
  51. data/spec/leadlight/representation_spec.rb +36 -4
  52. data/spec/leadlight/request_spec.rb +39 -22
  53. data/spec/leadlight/service_spec.rb +14 -13
  54. data/spec/leadlight/tint_helper_spec.rb +36 -2
  55. data/spec/leadlight_spec.rb +31 -17
  56. data/spec/support/link_matchers.rb +59 -0
  57. data/spec/support/misc.rb +9 -0
  58. metadata +49 -34
@@ -3,6 +3,8 @@ require 'leadlight/link'
3
3
 
4
4
  module Leadlight
5
5
  describe Link do
6
+ include LinkMatchers
7
+
6
8
  subject { Link.new(service, href, rel, title, options) }
7
9
  let(:service) { stub(:service, get: nil) }
8
10
  let(:href) { '/TEST_PATH' }
@@ -10,9 +12,26 @@ module Leadlight
10
12
  let(:title) { 'TEST_TITLE' }
11
13
  let(:options) { {} }
12
14
 
15
+ it { should expand_to('/TEST_PATH?foo=bar').given(:foo => 'bar') }
16
+ it {
17
+ should expand_to('/TEST_PATH?foo=bar&x=42').
18
+ given(:foo => 'bar', "x" => 42)
19
+ }
20
+ it {
21
+ should expand_to('/TEST_PATH?foo[0]=123&foo[1]=456').
22
+ given(:foo => [123,456])
23
+ }
24
+
25
+ describe '#expand' do
26
+ it 'returns self given no arguments' do
27
+ subject.expand.should equal(subject)
28
+ end
29
+ end
30
+
13
31
  describe '#follow' do
14
32
  it 'calls service.get with the href' do
15
- service.should_receive(:get_representation!).with(Addressable::URI.parse(href))
33
+ service.should_receive(:get_representation!).
34
+ with(Addressable::URI.parse(href), anything, anything)
16
35
  subject.follow
17
36
  end
18
37
 
@@ -26,28 +45,69 @@ module Leadlight
26
45
  end
27
46
  end
28
47
 
29
- def self.delegates_to_service(method_name)
48
+ def self.delegates_to_service(method_name, has_body)
30
49
  it "delegates ##{method_name} to the service" do
31
50
  yielded = :nothing
32
51
  args = [:foo, :bar]
33
52
  request = stub(:request)
34
53
  representation = stub(:representation)
54
+ if has_body
55
+ args.unshift nil
56
+ end
35
57
  service.should_receive(method_name).
36
- with(subject.href, *args).
58
+ with(subject.href, nil, :foo, :bar, anything).
37
59
  and_yield(representation).
38
60
  and_return(request)
39
61
  subject.public_send(method_name, *args) do |rep|
40
62
  yielded = rep
41
63
  end.should equal(request)
42
64
  end
65
+
66
+ it "adds itself to the options for ##{method_name}" do
67
+ yielded = :nothing
68
+ args = [:foo, :bar, {baz: "buz"}]
69
+ request = stub(:request)
70
+ representation = stub(:representation)
71
+ if has_body
72
+ args.unshift nil
73
+ end
74
+ service.should_receive(method_name).
75
+ with(subject.href, nil, :foo, :bar, {baz: "buz", link: subject})
76
+ subject.public_send(method_name, *args)
77
+ end
78
+
79
+ it "adds missing request options hash to calls to ##{method_name}" do
80
+ yielded = :nothing
81
+ args = [:foo, :bar]
82
+ request = stub(:request)
83
+ representation = stub(:representation)
84
+ if has_body
85
+ args.unshift nil
86
+ end
87
+ service.should_receive(method_name).
88
+ with(subject.href, nil, :foo, :bar, {link: subject})
89
+ subject.public_send(method_name, *args)
90
+ end
43
91
  end
44
92
 
45
- delegates_to_service :get
46
- delegates_to_service :post
47
- delegates_to_service :head
48
- delegates_to_service :options
49
- delegates_to_service :put
50
- delegates_to_service :delete
51
- delegates_to_service :patch
93
+ # method has_body
94
+ delegates_to_service :get, false
95
+ delegates_to_service :post, true
96
+ delegates_to_service :head, false
97
+ delegates_to_service :options, false
98
+ delegates_to_service :put, true
99
+ delegates_to_service :delete, false
100
+ delegates_to_service :patch, true
101
+
102
+
103
+ describe '#params' do
104
+ context 'when explicitly specified' do
105
+ it 'gives explicit params priority over query params' do
106
+ href = "/somepath?foo=baz&fizz=buzz"
107
+ link = Link.new(service, href, rel, title, :expansion_params => {:foo => 'bar'})
108
+ link.params.should eq('foo' => 'bar', 'fizz' => 'buzz')
109
+ end
110
+ end
111
+ end
52
112
  end
53
113
  end
@@ -5,6 +5,8 @@ require 'leadlight/type_map'
5
5
  module Leadlight
6
6
  describe LinkTemplate do
7
7
 
8
+ include LinkMatchers
9
+
8
10
  # TODO: This setup is loony. Refactor.
9
11
  subject { LinkTemplate.new(service, href, rel, title, options) }
10
12
  let(:service) { stub(:service, type_map: TypeMap.new) }
@@ -22,19 +24,33 @@ module Leadlight
22
24
  service.stub(:get_representation!).and_yield(result).and_return(request)
23
25
  end
24
26
 
27
+ it { should(expand_to('/TEST_PATH/N/M/?foo=bar').
28
+ with_params('n' => 'N', 'm' => 'M').
29
+ given(:n => "N", :m => "M", :foo => 'bar'))}
30
+
31
+ it { should(expand_to('/TEST_PATH/N/M/?foo=bar&x=42').
32
+ given(:n => "N", :m => "M", :foo => 'bar', "x" => 42))}
33
+
34
+ it { should(expand_to('/TEST_PATH/N/M/?foo[0]=123&foo[1]=456').
35
+ given(:n => "N", :m => "M", :foo => [123,456])) }
36
+
25
37
  describe '#follow' do
26
38
  it 'calls service.get with the expanded href' do
27
- service.should_receive(:get_representation!).with('/TEST_PATH/23/42/')
39
+ service.should_receive(:get_representation!).
40
+ with(u('/TEST_PATH/23/42/'))
28
41
  subject.follow(23,42)
29
42
  end
30
43
 
31
44
  it 'can accept a hash for template parameters' do
32
- service.should_receive(:get_representation!).with('/TEST_PATH/AA/BB/')
45
+ service.should_receive(:get_representation!).
46
+ with(u('/TEST_PATH/AA/BB/'))
33
47
  subject.follow(:n => 'AA', 'm' => 'BB')
34
48
  end
35
49
 
36
- it 'leaves unrecognized params in the params hash alone' do
37
- service.should_receive(:get_representation!).with('/TEST_PATH/AA/BB/', {'other' => 'XX'})
50
+
51
+ it 'tacks unrecognized on as query params' do
52
+ service.should_receive(:get_representation!).
53
+ with(u('/TEST_PATH/AA/BB/?other=XX'))
38
54
  subject.follow(:n => 'AA', 'm' => 'BB', :other => 'XX')
39
55
  end
40
56
 
@@ -0,0 +1,14 @@
1
+ require 'spec_helper_lite'
2
+ require 'leadlight/null_link'
3
+
4
+ module Leadlight
5
+ describe NullLink do
6
+ subject {
7
+ NullLink.new("http://example.org/z/y/?foo=123&bar=456&bar=789")
8
+ }
9
+
10
+ it 'derives params from URL query string' do
11
+ subject.params.should eq("foo" => "123", "bar" => "789")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper_lite'
2
+ require 'leadlight/param_hash'
3
+
4
+ module Leadlight
5
+ describe ParamHash do
6
+ include Leadlight
7
+ it 'converts values to strings' do
8
+ result = ParamHash(:foo => 123, :bar => :baz, :buz => 2.3)
9
+ result.should eq({:foo => "123", :bar => "baz", :buz => "2.3"})
10
+ end
11
+
12
+ it 'converts arrays to arrays of strings' do
13
+ result = ParamHash(:foo => [:bar, 123, 3.14])
14
+ result.should eq(:foo => ["bar", "123", "3.14"])
15
+ end
16
+
17
+
18
+ it 'converts values recursively' do
19
+ result = ParamHash(:h =>
20
+ {:foo => 123, :bar => :baz, :buz => 2.3})
21
+ result.should eq(:h =>
22
+ {:foo => "123", :bar => "baz", :buz => "2.3"})
23
+ end
24
+
25
+ end
26
+ end
@@ -11,6 +11,10 @@ module Leadlight
11
11
  let(:service) { stub(:service, tints: [TintA, TintB]) }
12
12
  let(:location) { stub(:location) }
13
13
  let(:response) { stub(:response) }
14
+ let(:request_params) { stub(:request_params) }
15
+ let(:link) { stub(:link) }
16
+ let(:request) { stub(:request, params: request_params, link: link) }
17
+ let(:captures) { stub(:captures) }
14
18
 
15
19
  it 'has a __service__ accessor' do
16
20
  subject.__service__ = service
@@ -27,23 +31,51 @@ module Leadlight
27
31
  subject.__response__.should equal(response)
28
32
  end
29
33
 
34
+ it 'has a __request__ accessor' do
35
+ subject.__request__ = request
36
+ subject.__request__.should equal(request)
37
+ end
38
+
39
+ it 'has a __link__ accessor' do
40
+ subject.__request__ = request
41
+ subject.__link__.should equal(link)
42
+ end
43
+
44
+ it 'has a __request_params__ accessor' do
45
+ subject.__request__ = request
46
+ subject.__request_params__.should equal(request_params)
47
+ end
48
+
49
+ describe '#__captures__' do
50
+ it 'defaults to an empty hash' do
51
+ subject.__captures__.should eq({})
52
+ end
53
+ end
54
+
30
55
  describe '#initialize_representation' do
31
- it 'sets __service__, __location__, and __response__' do
32
- subject.initialize_representation(service, location, response)
56
+ it 'sets __service__, __location__, __response__, and __request__' do
57
+ subject.initialize_representation(service, location, response, request)
33
58
  subject.__service__.should equal(service)
34
59
  subject.__location__.should equal(location)
35
60
  subject.__response__.should equal(response)
61
+ subject.__request__.should equal(request)
36
62
  end
37
63
 
38
64
  it 'returns self' do
39
- result = subject.initialize_representation(service, location, response)
65
+ result = subject.initialize_representation(service,
66
+ location,
67
+ response,
68
+ request)
40
69
  result.should equal(subject)
41
70
  end
42
71
 
43
72
  end
44
73
  describe '#apply_all_tints' do
45
74
  before do
46
- subject.initialize_representation(service, location, response)
75
+ subject.initialize_representation(service,
76
+ location,
77
+ response,
78
+ request_params)
47
79
  end
48
80
 
49
81
  it 'extends object with tints from service' do
@@ -47,19 +47,22 @@ module Leadlight
47
47
  end
48
48
  end
49
49
 
50
- subject { Request.new(service, connection, url, http_method, params, body) }
50
+ subject { Request.new(service, connection, url, http_method, body, options) }
51
51
  let(:service) { stub(:service, :type_map => type_map) }
52
52
  let(:type_map) { stub(:type_map).as_null_object }
53
53
  let(:connection) { stub(:connection, :run_request => faraday_response) }
54
- let(:url) { stub(:url) }
54
+ let(:url) { stub(:url, :to_s => "STRINGIFIED_URL") }
55
55
  let(:http_method){ :get }
56
56
  let(:body) { stub(:body) }
57
- let(:params) { {} }
58
- let(:faraday_request) {stub(:faraday_request, options: {})}
57
+ let(:faraday_request) {stub(:faraday_request, options: {}, params: request_params)}
59
58
  let(:on_complete_handlers) { [] }
60
- let(:faraday_env) { {} }
59
+ let(:faraday_env) { {request: faraday_request} }
61
60
  let(:representation) { stub(:representation) }
62
61
  let(:faraday_response) { FakeFaradayResponse.new(faraday_env) }
62
+ let(:link) { stub(:link, params: link_params) }
63
+ let(:link_params) { { a: "123", b: "456" } }
64
+ let(:request_params) { { b: "789", c: "321" } }
65
+ let(:options) { { link: link } }
63
66
 
64
67
  def run_completion_handlers
65
68
  faraday_env[:status] ||= 200
@@ -107,7 +110,7 @@ module Leadlight
107
110
 
108
111
  it "starts a request runnning" do
109
112
  connection.should_receive(:run_request).
110
- with(http_method, url, anything, {}).
113
+ with(http_method, "STRINGIFIED_URL", anything, {}).
111
114
  and_return(faraday_response)
112
115
  subject.submit
113
116
  end
@@ -121,21 +124,6 @@ module Leadlight
121
124
  yielded.should equal(faraday_request)
122
125
  end
123
126
 
124
- context "with request params" do
125
- let(:params) {{ query: 'value' }}
126
-
127
- it "passes params to request" do
128
- request_params.should_receive(:update).with(params)
129
- subject.submit
130
- end
131
- end
132
-
133
- context "with no request params" do
134
- it "doesn't alter request params" do
135
- request_params.should_not_receive(:update)
136
- subject.submit
137
- end
138
- end
139
127
  end
140
128
 
141
129
  shared_examples_for "synchronous methods" do
@@ -309,7 +297,36 @@ module Leadlight
309
297
  subject.should_receive(:raise).with(representation)
310
298
  submit_and_complete
311
299
  end
312
-
300
+
301
+ end
302
+
303
+ describe "#link" do
304
+ it "defaults to a null link with the URL passed in" do
305
+ subject = Request.new(service, connection, url, http_method, body)
306
+ subject.link.should eq(NullLink.new(url))
307
+ end
308
+
309
+ it 'is taken from options supplied to constructor' do
310
+ link = double
311
+ subject = Request.new(service, connection, url, http_method, body, link: link)
312
+ subject.link.should be(link)
313
+ end
314
+ end
315
+
316
+ describe "#params" do
317
+ context "(after completion)" do
318
+ def do_it(&block)
319
+ subject.submit_and_wait(&block)
320
+ end
321
+
322
+ before do
323
+ do_it_and_complete
324
+ end
325
+
326
+ it 'merges request params and link params' do
327
+ subject.params.should eq(a: "123", b: "789", c: "321")
328
+ end
329
+ end
313
330
  end
314
331
 
315
332
  end
@@ -4,13 +4,13 @@ require 'leadlight/service'
4
4
  module Leadlight
5
5
  describe Service do
6
6
  subject { klass.new(service_options) }
7
- let(:klass) {
8
- Class.new do
7
+ let(:klass) {
8
+ Class.new do
9
9
  include Service
10
-
10
+
11
11
  def execute_hook(*)
12
12
  end
13
- end
13
+ end
14
14
  }
15
15
  let(:connection) { stub(:connection, get: response) }
16
16
  let(:representation) { stub(:representation) }
@@ -22,7 +22,7 @@ module Leadlight
22
22
  let(:request_class) { stub(:request_class, new: request) }
23
23
 
24
24
  before do
25
- subject.stub(connection: connection,
25
+ subject.stub(connection: connection,
26
26
  url: nil,
27
27
  request_class: request_class)
28
28
  end
@@ -62,20 +62,21 @@ module Leadlight
62
62
  subject.public_send(http_method, '/somepath')
63
63
  end
64
64
 
65
- it 'passes the params to the request' do
66
- params = stub
65
+ it 'passes the body to the request' do
66
+ body = stub
67
67
  request_class.should_receive(:new).
68
- with(anything, anything, anything, anything, params, anything).
68
+ with(anything, anything, anything, anything, body, anything).
69
69
  and_return(request)
70
- subject.public_send(http_method, '/somepath', params)
70
+ subject.public_send(http_method, '/somepath', body)
71
71
  end
72
72
 
73
- it 'passes the body to the request' do
74
- body = stub
73
+ it 'passes the originating link to the request' do
74
+ link = double(:link)
75
75
  request_class.should_receive(:new).
76
- with(anything, anything, anything, anything, anything, body).
76
+ with(anything, anything, anything, anything, anything,
77
+ hash_including(link: link)).
77
78
  and_return(request)
78
- subject.public_send(http_method, '/somepath', {}, body)
79
+ subject.public_send(http_method, '/somepath', anything, link: link)
79
80
  end
80
81
 
81
82
  context 'given a block' do
@@ -5,8 +5,9 @@ module Leadlight
5
5
  describe TintHelper do
6
6
  subject{ TintHelper.new(object, tint) }
7
7
  let(:object) {
8
- stub(__location__: stub(path: '/the/path'),
9
- __response__: response)
8
+ stub(__location__: Addressable::URI.parse('/the/path'),
9
+ __response__: response,
10
+ __captures__: captures)
10
11
  }
11
12
  let(:response) {
12
13
  stub(:response,
@@ -18,6 +19,7 @@ module Leadlight
18
19
  { 'Content-Type' => 'text/html; charset=UTF-8'}
19
20
  }
20
21
  let(:tint) { Module.new }
22
+ let(:captures) { {} }
21
23
 
22
24
  it 'forwards unknown calls to the wrapped object' do
23
25
  object.should_receive(:foo).with('bar')
@@ -41,6 +43,13 @@ module Leadlight
41
43
  end
42
44
  end
43
45
 
46
+ it 'adds regex captures to representation captures' do
47
+ subject.exec_tint do
48
+ match_path(%r{/(?<x>\w+)/(?<y>\w+)})
49
+ end
50
+ captures.should eq('x' => 'the', 'y' => 'path')
51
+ end
52
+
44
53
  it 'does not allow execution to proceed on no match' do
45
54
  object.should_not_receive(:baz)
46
55
  subject.exec_tint do
@@ -50,6 +59,31 @@ module Leadlight
50
59
  end
51
60
  end
52
61
 
62
+ describe '#match_template' do
63
+ it 'allows execution to proceed on match' do
64
+ object.should_receive(:baz)
65
+ subject.exec_tint do
66
+ match_template('/{a}/{b}')
67
+ baz
68
+ end
69
+ end
70
+
71
+ it 'halts execution on no match' do
72
+ object.should_not_receive(:baz)
73
+ subject.exec_tint do
74
+ match_template('/{a}/{b}/{c}')
75
+ baz
76
+ end
77
+ end
78
+
79
+ it 'adds mappings to representation captures' do
80
+ subject.exec_tint do
81
+ match_template('/{a}/{b}')
82
+ end
83
+ captures.should eq('a' => 'the', 'b' => 'path')
84
+ end
85
+ end
86
+
53
87
  describe '#match_content_type' do
54
88
  it 'allows execution to proceed on match' do
55
89
  object.should_receive(:baz)