leadlight 0.0.2

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 (54) hide show
  1. data/Gemfile +9 -0
  2. data/Gemfile.lock +79 -0
  3. data/Guardfile +19 -0
  4. data/Rakefile +133 -0
  5. data/leadlight.gemspec +125 -0
  6. data/lib/leadlight.rb +92 -0
  7. data/lib/leadlight/blank.rb +15 -0
  8. data/lib/leadlight/codec.rb +63 -0
  9. data/lib/leadlight/enumerable_representation.rb +24 -0
  10. data/lib/leadlight/errors.rb +14 -0
  11. data/lib/leadlight/hyperlinkable.rb +126 -0
  12. data/lib/leadlight/link.rb +35 -0
  13. data/lib/leadlight/link_template.rb +47 -0
  14. data/lib/leadlight/representation.rb +30 -0
  15. data/lib/leadlight/request.rb +91 -0
  16. data/lib/leadlight/service.rb +73 -0
  17. data/lib/leadlight/service_middleware.rb +50 -0
  18. data/lib/leadlight/tint.rb +26 -0
  19. data/lib/leadlight/tint_helper.rb +67 -0
  20. data/lib/leadlight/type.rb +71 -0
  21. data/spec/cassettes/Leadlight/authorized_GitHub_example/_user/has_the_expected_content.yml +75 -0
  22. data/spec/cassettes/Leadlight/authorized_GitHub_example/_user/indicates_the_expected_oath_scopes.yml +75 -0
  23. data/spec/cassettes/Leadlight/authorized_GitHub_example/adding_and_removing_team_members.yml +384 -0
  24. data/spec/cassettes/Leadlight/authorized_GitHub_example/adding_and_removing_team_members/.yml +309 -0
  25. data/spec/cassettes/Leadlight/authorized_GitHub_example/test_team/.yml +159 -0
  26. data/spec/cassettes/Leadlight/basic_GitHub_example/_root/.yml +32 -0
  27. data/spec/cassettes/Leadlight/basic_GitHub_example/_root/__location__/.yml +32 -0
  28. data/spec/cassettes/Leadlight/basic_GitHub_example/_root/should_be_a_204_no_content.yml +32 -0
  29. data/spec/cassettes/Leadlight/tinted_GitHub_example/_root/.yml +32 -0
  30. data/spec/cassettes/Leadlight/tinted_GitHub_example/_root/__location__/.yml +32 -0
  31. data/spec/cassettes/Leadlight/tinted_GitHub_example/_root/should_be_a_204_no_content.yml +32 -0
  32. data/spec/cassettes/Leadlight/tinted_GitHub_example/_user/has_the_expected_content.yml +65 -0
  33. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/.yml +100 -0
  34. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_be_able_to_follow_next_link.yml +135 -0
  35. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_be_enumerable.yml +275 -0
  36. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_be_enumerable_over_page_boundaries.yml +170 -0
  37. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_have_next_and_last_links.yml +100 -0
  38. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_link/exists.yml +32 -0
  39. data/spec/cassettes/Leadlight/tinted_GitHub_example/user_link/links_to_the_expected_URL.yml +32 -0
  40. data/spec/leadlight/codec_spec.rb +93 -0
  41. data/spec/leadlight/hyperlinkable_spec.rb +136 -0
  42. data/spec/leadlight/link_spec.rb +53 -0
  43. data/spec/leadlight/link_template_spec.rb +45 -0
  44. data/spec/leadlight/representation_spec.rb +62 -0
  45. data/spec/leadlight/request_spec.rb +236 -0
  46. data/spec/leadlight/service_middleware_spec.rb +81 -0
  47. data/spec/leadlight/service_spec.rb +127 -0
  48. data/spec/leadlight/tint_helper_spec.rb +132 -0
  49. data/spec/leadlight/type_spec.rb +137 -0
  50. data/spec/leadlight_spec.rb +237 -0
  51. data/spec/spec_helper_lite.rb +6 -0
  52. data/spec/support/credentials.rb +16 -0
  53. data/spec/support/vcr.rb +18 -0
  54. metadata +229 -0
@@ -0,0 +1,32 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://api.github.com/
6
+ body: ""
7
+ headers:
8
+ Accept:
9
+ - application/json, text/x-yaml, application/xml, application/xhtml+xml, text/html, text/plain
10
+ response:
11
+ status:
12
+ code: 204
13
+ message:
14
+ headers:
15
+ server:
16
+ - nginx/1.0.4
17
+ date:
18
+ - Mon, 09 Jan 2012 02:31:45 GMT
19
+ connection:
20
+ - close
21
+ status:
22
+ - 204 No Content
23
+ x-ratelimit-limit:
24
+ - "5000"
25
+ etag:
26
+ - "\"d41d8cd98f00b204e9800998ecf8427e\""
27
+ x-ratelimit-remaining:
28
+ - "4993"
29
+ body: ""
30
+ http_version:
31
+ recorded_at: Mon, 09 Jan 2012 02:31:43 GMT
32
+ recorded_with: VCR 2.0.0.rc1
@@ -0,0 +1,32 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://api.github.com/
6
+ body: ""
7
+ headers:
8
+ Accept:
9
+ - application/json, text/x-yaml, application/xml, application/xhtml+xml, text/html, text/plain
10
+ response:
11
+ status:
12
+ code: 204
13
+ message:
14
+ headers:
15
+ server:
16
+ - nginx/1.0.4
17
+ date:
18
+ - Mon, 09 Jan 2012 02:31:45 GMT
19
+ connection:
20
+ - close
21
+ status:
22
+ - 204 No Content
23
+ x-ratelimit-limit:
24
+ - "5000"
25
+ etag:
26
+ - "\"d41d8cd98f00b204e9800998ecf8427e\""
27
+ x-ratelimit-remaining:
28
+ - "4992"
29
+ body: ""
30
+ http_version:
31
+ recorded_at: Mon, 09 Jan 2012 02:31:44 GMT
32
+ recorded_with: VCR 2.0.0.rc1
@@ -0,0 +1,93 @@
1
+ require 'spec_helper_lite'
2
+ require 'leadlight/codec'
3
+
4
+ module Leadlight
5
+ describe Codec do
6
+ let(:subject) {
7
+ # Due to the static init in Codec, we need to reload the class
8
+ # after stubs/mocks are set up
9
+ load 'leadlight/codec.rb'
10
+ Codec.new
11
+ }
12
+ let(:representation) { stub(:representation) }
13
+ let(:entity_body) { stub(:entity_body) }
14
+
15
+ it 'decodes JSON' do
16
+ MultiJson.should_receive(:decode).
17
+ with(entity_body, anything).
18
+ and_return(representation)
19
+ subject.decode('application/json', entity_body).
20
+ should equal(representation)
21
+ end
22
+
23
+ it 'decodes types ending in +json as JSON' do
24
+ MultiJson.should_receive(:decode).
25
+ with(entity_body, anything).
26
+ and_return(representation)
27
+ subject.decode('application/vnc.example.user+json foo=bar', entity_body).
28
+ should equal(representation)
29
+ end
30
+
31
+ it 'decodes text/plain as a string' do
32
+ subject.decode('text/plain', entity_body).should eq(entity_body)
33
+ end
34
+
35
+ it 'barfs on unrecognized content-type' do
36
+ expect{ subject.decode('xyzzy', entity_body) }.
37
+ to raise_error(ArgumentError)
38
+ end
39
+
40
+ it 'ignores junk after the content type' do
41
+ subject.decode('text/plain foo=bar', entity_body).should eq(entity_body)
42
+ end
43
+
44
+ it 'ignores whitespace in the content type' do
45
+ subject.decode(' text/plain foo=bar', entity_body).should eq(entity_body)
46
+ end
47
+
48
+ it 'passes options to the JSON decoder' do
49
+ MultiJson.should_receive(:decode).
50
+ with(anything, {foo: 42})
51
+ subject.decode('application/json', entity_body, foo: 42)
52
+ end
53
+
54
+ it 'decodes JSON with multi_json' do
55
+ MultiJson.should_receive(:decode).
56
+ with(entity_body, anything).
57
+ and_return(representation)
58
+ subject.decode('application/json', entity_body).
59
+ should equal(representation)
60
+ end
61
+
62
+ it 'encodes types ending in +json as JSON' do
63
+ MultiJson.should_receive(:encode).
64
+ with(representation, anything).
65
+ and_return(entity_body)
66
+ subject.encode('application/vnc.example.user+json foo=bar', representation).
67
+ should equal(entity_body)
68
+ end
69
+
70
+ it 'encodes text/plain as a string' do
71
+ subject.encode('text/plain', representation).should eq(representation.to_s)
72
+ end
73
+
74
+ it 'barfs on encoding unrecognized content-type' do
75
+ expect{ subject.encode('xyzzy', representation) }.
76
+ to raise_error(ArgumentError)
77
+ end
78
+
79
+ it 'ignores junk after the content type for encoding' do
80
+ subject.encode('text/plain foo=bar', representation).should eq(representation.to_s)
81
+ end
82
+
83
+ it 'ignores whitespace in the content type for encoding' do
84
+ subject.encode(' text/plain foo=bar', representation).should eq(representation.to_s)
85
+ end
86
+
87
+ it 'passes options to the JSON encoder' do
88
+ MultiJson.should_receive(:encode).
89
+ with(anything, {foo: 42})
90
+ subject.encode('application/json', representation, foo: 42)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,136 @@
1
+ require 'spec_helper_lite'
2
+ require 'leadlight/hyperlinkable'
3
+
4
+ module Leadlight
5
+ describe Hyperlinkable do
6
+ subject { representation.extend(Hyperlinkable) }
7
+ let(:representation) { Object.new }
8
+ let(:response) { stub(:response, env: env) }
9
+ let(:response_url) { Addressable::URI.parse('/foo') }
10
+ let(:request) { stub(:request) }
11
+ let(:env) {
12
+ {
13
+ url: '/foo',
14
+ leadlight_service: service,
15
+ :response_headers => headers
16
+ }
17
+ }
18
+ let(:service) { stub(url: 'http://example.com') }
19
+ let(:headers) { {'Link' => links} }
20
+ let(:links) {
21
+ '<https://api.github.com/users/avdi/followers?page=2>; rel="next",'\
22
+ ' <https://api.github.com/users/avdi/followers?page=6>; rel="last"'
23
+ }
24
+
25
+ before do
26
+ representation.stub(__response__: response, __service__: service)
27
+ end
28
+
29
+ describe '.extend' do
30
+
31
+ it 'adds itself to the representation' do
32
+ subject.should be_a(Hyperlinkable)
33
+ end
34
+
35
+ it 'adds a self link' do
36
+ subject.link('self').should be_a(Link)
37
+ subject.link('self').href.to_s.should eq('/foo')
38
+ subject.link('self').rel.should eq('self')
39
+ subject.link('self').rev.should eq('self')
40
+ subject.link('self').title.should eq('self')
41
+ end
42
+
43
+ it 'adds links from Link header' do
44
+ subject.link('next').href.to_s.
45
+ should eq('https://api.github.com/users/avdi/followers?page=2')
46
+ subject.link('last').href.to_s.
47
+ should eq('https://api.github.com/users/avdi/followers?page=6')
48
+ end
49
+
50
+ end
51
+
52
+ describe '#add_link' do
53
+ it 'swells the links collection' do
54
+ expect { subject.add_link('/foo') }.to change{ subject.links.size }.by(1)
55
+ end
56
+
57
+ it 'expands links' do
58
+ subject.add_link('/parent', 'parent')
59
+ subject.link('parent').href.to_s.should eq('/parent')
60
+ end
61
+
62
+ it 'adds a Link' do
63
+ subject.add_link('/parent', 'parent')
64
+ subject.link('parent').should be_a(Link)
65
+ end
66
+
67
+ it 'adds a link helper' do
68
+ subject.add_link('/parent', 'parent')
69
+ service.should_receive(:get_representation!).
70
+ with(Addressable::URI.parse('/parent')).
71
+ and_return(request)
72
+ subject.parent
73
+ end
74
+ end
75
+
76
+ describe '#add_link_template' do
77
+ it 'adds a link' do
78
+ expect { subject.add_link_template('/foo/{id}') }.to change{ subject.links.size }.by(1)
79
+ end
80
+
81
+ it 'expands links' do
82
+ subject.add_link_template('/child/{index}', 'child')
83
+ href = subject.link('child').href
84
+ href.to_s.should eq('/child/{index}')
85
+ end
86
+
87
+ it 'adds a LinkTemplate' do
88
+ subject.add_link_template('/child/{index}', 'child')
89
+ subject.link('child').should be_a(LinkTemplate)
90
+ end
91
+
92
+ it 'adds a link helper' do
93
+ subject.add_link_template('/child/{index}', 'child')
94
+ service.should_receive(:get_representation!).
95
+ with('/child/23')
96
+ subject.child(23)
97
+ end
98
+
99
+ end
100
+
101
+ describe '#add_link_set' do
102
+ before do
103
+ subject.add_link_set('bourbon') do
104
+ [
105
+ {href: 'http://example.com/blantons', title: 'Blantons'},
106
+ {href: 'http://example.com/bookers', title: 'Bookers'},
107
+ {href: 'http://example.com/knobcreek', title: 'Knob Creek', aliases: ['KC']},
108
+ ]
109
+ end
110
+ end
111
+
112
+ it 'adds links for each member of the set' do
113
+ links = subject.links('bourbon').map(&:href).map(&:to_s)
114
+ links.should include('http://example.com/blantons')
115
+ links.should include('http://example.com/bookers')
116
+ links.should include('http://example.com/knobcreek')
117
+ end
118
+
119
+ it 'establishes a link helper that finds by title' do
120
+ result_stubs = [stub, stub, stub]
121
+ service.should_receive(:get_representation!).and_yield(result_stubs[0])
122
+ service.should_receive(:get_representation!).and_yield(result_stubs[1])
123
+ service.should_receive(:get_representation!).and_yield(result_stubs[2])
124
+ subject.bourbon('Blantons').should equal(result_stubs[0])
125
+ subject.bourbon('Bookers').should equal(result_stubs[1])
126
+ subject.bourbon('Knob Creek').should equal(result_stubs[2])
127
+ end
128
+
129
+ it 'sets up the link helper to handle aliases' do
130
+ result = stub
131
+ service.should_receive(:get_representation!).and_yield(result)
132
+ subject.bourbon('KC').should equal(result)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper_lite'
2
+ require 'leadlight/link'
3
+
4
+ module Leadlight
5
+ describe Link do
6
+ subject { Link.new(service, href, rel, title, options) }
7
+ let(:service) { stub(:service, get: nil) }
8
+ let(:href) { '/TEST_PATH' }
9
+ let(:rel) { 'TEST_REL' }
10
+ let(:title) { 'TEST_TITLE' }
11
+ let(:options) { {} }
12
+
13
+ describe '#follow' do
14
+ it 'calls service.get with the href' do
15
+ service.should_receive(:get_representation!).with(Addressable::URI.parse(href))
16
+ subject.follow
17
+ end
18
+
19
+ it 'returns the result of the get' do
20
+ request = stub
21
+ result = stub
22
+ service.should_receive(:get_representation!).
23
+ and_yield(result).
24
+ and_return(request)
25
+ subject.follow.should equal(result)
26
+ end
27
+ end
28
+
29
+ def self.delegates_to_service(method_name)
30
+ it "delegates ##{method_name} to the service" do
31
+ yielded = :nothing
32
+ args = [:foo, :bar]
33
+ request = stub(:request)
34
+ representation = stub(:representation)
35
+ service.should_receive(method_name).
36
+ with(subject.href, *args).
37
+ and_yield(representation).
38
+ and_return(request)
39
+ subject.public_send(method_name, *args) do |rep|
40
+ yielded = rep
41
+ end.should equal(request)
42
+ end
43
+ end
44
+
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
52
+ end
53
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper_lite'
2
+ require 'leadlight/link_template'
3
+
4
+ module Leadlight
5
+ describe LinkTemplate do
6
+
7
+ # TODO: This setup is loony. Refactor.
8
+ subject { LinkTemplate.new(service, href, rel, title, options) }
9
+ let(:service) { stub(:service) }
10
+ let(:request) { stub(:request) }
11
+ let(:result) { stub(:result) }
12
+ let(:href) { '/TEST_PATH/{n}/{m}/' }
13
+ let(:rel) { 'TEST_REL' }
14
+ let(:title) { 'TEST_TITLE' }
15
+ let(:options) { {} }
16
+ let(:mapping) { {'n' => 'N_VALUE', 'm' => 'M_VALUE'} }
17
+ let(:values) { mapping.values }
18
+
19
+ before do
20
+ request.stub(raise_on_error: request)
21
+ service.stub(:get_representation!).and_yield(result).and_return(request)
22
+ end
23
+
24
+ describe '#follow' do
25
+ it 'calls service.get with the expanded href' do
26
+ service.should_receive(:get_representation!).with('/TEST_PATH/23/42/')
27
+ subject.follow(23,42)
28
+ end
29
+
30
+ it 'can accept a hash for template parameters' do
31
+ service.should_receive(:get_representation!).with('/TEST_PATH/AA/BB/')
32
+ subject.follow(:n => 'AA', 'm' => 'BB')
33
+ end
34
+
35
+ it 'leaves unrecognized params in the params hash alone' do
36
+ service.should_receive(:get_representation!).with('/TEST_PATH/AA/BB/', {'other' => 'XX'})
37
+ subject.follow(:n => 'AA', 'm' => 'BB', :other => 'XX')
38
+ end
39
+
40
+ it 'returns the result of the get' do
41
+ subject.follow(23,42).should equal(result)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper_lite'
2
+ require 'leadlight/representation'
3
+
4
+ module Leadlight
5
+ describe Representation do
6
+ module TintA; end
7
+ module TintB; end
8
+
9
+ subject { object }
10
+ let(:object) { Object.new.extend(Representation) }
11
+ let(:service) { stub(:service, tints: [TintA, TintB]) }
12
+ let(:location) { stub(:location) }
13
+ let(:response) { stub(:response) }
14
+
15
+ it 'has a __service__ accessor' do
16
+ subject.__service__ = service
17
+ subject.__service__.should equal(service)
18
+ end
19
+
20
+ it 'has a __location__ accessor' do
21
+ subject.__location__ = location
22
+ subject.__location__.should equal(location)
23
+ end
24
+
25
+ it 'has a __response__ accessor' do
26
+ subject.__response__ = response
27
+ subject.__response__.should equal(response)
28
+ end
29
+
30
+ describe '#initialize_representation' do
31
+ it 'sets __service__, __location__, and __response__' do
32
+ subject.initialize_representation(service, location, response)
33
+ subject.__service__.should equal(service)
34
+ subject.__location__.should equal(location)
35
+ subject.__response__.should equal(response)
36
+ end
37
+
38
+ it 'returns self' do
39
+ result = subject.initialize_representation(service, location, response)
40
+ result.should equal(subject)
41
+ end
42
+
43
+ end
44
+ describe '#apply_all_tints' do
45
+ before do
46
+ subject.initialize_representation(service, location, response)
47
+ end
48
+
49
+ it 'extends object with tints from service' do
50
+ subject.apply_all_tints
51
+ subject.should be_a(TintA)
52
+ subject.should be_a(TintB)
53
+ end
54
+
55
+ it 'applies tints' do
56
+ object.should_receive(:__apply_tint__)
57
+ subject.apply_all_tints
58
+ end
59
+ end
60
+
61
+ end
62
+ end