leadlight 0.0.5 → 0.0.6

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.
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)