hexabat 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ require 'hexabat/importer'
2
+
3
+ describe Hexabat::Importer do
4
+ subject { described_class.new counter, request_creator }
5
+ let(:counter) { stub :issue_counter }
6
+ let(:request_creator) { stub :page_request_creator }
7
+
8
+ let(:issue_count) { stub :issue_count }
9
+
10
+ it 'imports the first page of open and closed issues' do
11
+ request_creator.should_receive(:for).with(page: 1, state: 'open')
12
+ request_creator.should_receive(:for).with(page: 1, state: 'closed')
13
+ subject.import
14
+ end
15
+
16
+ it 'counts issues and reports pages in the repository' do
17
+ page_range = stub(:page_range, multiple_pages?: false)
18
+ counter.should_receive(:counted).with(:first, :open, page_range, issue_count)
19
+ subject.first_page_retrieved(:open).call(page_range, issue_count)
20
+ end
21
+
22
+ it 'imports the last page of issues' do
23
+ page_range = stub(:page_range, multiple_pages?: true, last: 4)
24
+ counter.should_receive(:counted).with(:first, :open, page_range, issue_count)
25
+ request_creator.should_receive(:for).with(page: 4, state: 'open')
26
+ subject.first_page_retrieved(:open).call(page_range, issue_count)
27
+ end
28
+
29
+ it 'counts issues and imports the remaining pages' do
30
+ page_range = stub(:page_range, middle: 2..3)
31
+ counter.should_receive(:counted).with(:last, :open, page_range, issue_count)
32
+ request_creator.should_receive(:for).with(page: 2, state: 'open')
33
+ request_creator.should_receive(:for).with(page: 3, state: 'open')
34
+ subject.last_page_retrieved(:open).call(page_range, issue_count)
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ require 'hexabat/issue_count'
2
+
3
+ describe Hexabat::IssueCount do
4
+ subject { described_class.new &issue_count_known }
5
+ let(:issue_count_known) { lambda{|count| @count = count} }
6
+
7
+ it 'keeps track of the notified page ranges' do
8
+ subject.page_ranges.should eq Hash[open: nil, closed: nil]
9
+ end
10
+
11
+ it 'keeps track of the notified issue counts' do
12
+ initial_count = {
13
+ open: { first: nil, last: nil },
14
+ closed: { first: nil, last: nil }
15
+ }
16
+ subject.issue_counts.should eq initial_count
17
+ end
18
+
19
+ it 'can be notified with issue counts and page ranges' do
20
+ page_range = stub(:page_range, multiple_pages?: true)
21
+ subject.counted :first, :open, page_range, 10
22
+ subject.page_ranges.should eq Hash[open: page_range, closed: nil]
23
+ expected_count = {open: {first: 10, last: nil}, closed: {first: nil, last: nil}}
24
+ subject.issue_counts.should eq expected_count
25
+ end
26
+
27
+ it 'updates issue counts if there is only one page' do
28
+ page_range = stub(:page_range, multiple_pages?: false, middle_page_count: 0)
29
+ subject.counted :first, :open, page_range, 2
30
+ expected_count = {open: {first: 2, last: 0}, closed: {first: nil, last: nil}}
31
+ subject.issue_counts.should eq expected_count
32
+ end
33
+
34
+ it 'calls the provided callback when done counting' do
35
+ open_page_range = stub(:page_range, multiple_pages?: false, middle_page_count: 0)
36
+ closed_page_range = stub(:page_range, multiple_pages?: true, middle_page_count: 2)
37
+ subject.counted :first, :open, open_page_range, 15
38
+ subject.counted :first, :closed, closed_page_range, 100
39
+ subject.counted :last, :closed, closed_page_range, 10
40
+ @count.should be 325
41
+ end
42
+ end
@@ -0,0 +1,121 @@
1
+ require 'hexabat/page_range'
2
+
3
+ describe Hexabat::PageRange do
4
+ context 'building page ranges from headers' do
5
+ it 'creates a SinglePageRange if there is only one page' do
6
+ headers = { 'NO LINK' => '' }
7
+ Hexabat::SinglePageRange.should_receive(:new).with(1)
8
+ described_class.from(headers)
9
+ end
10
+
11
+ it 'creates a MultiplePageRange if there is more than one page' do
12
+ headers = { 'LINK' => '<https://xxx.com?page=2&per_page=100>; rel="next", <https://xxx.com?page=5&per_page=100>; rel="last"' }
13
+ Hexabat::MultiplePageRange.should_receive(:new).with(1, 5)
14
+ described_class.from(headers)
15
+ end
16
+ end
17
+ end
18
+
19
+ describe Hexabat::PageRange::LinkHeader do
20
+ it 'extracts the last page from the LINK header' do
21
+ link = '<https://xxx.com?page=2&per_page=100>; rel="next", <https://xxx.com?page=8&per_page=100>; rel="last"'
22
+ described_class.new(link).last.should be 8
23
+ end
24
+
25
+ it 'calculates the last page from the LINK header when last is present' do
26
+ link = '<https://xxx.com?page=1&per_page=100&state=closed>; rel="last", <https://xxx.com?page=1&per_page=100&state=closed>; rel="first", <https://xxx.com?page=73&per_page=100&state=closed>; rel="prev"'
27
+ described_class.new(link).last.should be 74
28
+ end
29
+
30
+ it 'calculates the last page from the LINK header when last is missing' do
31
+ link = '<https://xxx.com?page=1&per_page=100&state=closed>; rel="first", <https://xxx.com?page=73&per_page=100&state=closed>; rel="prev"'
32
+ described_class.new(link).last.should be 74
33
+ end
34
+ end
35
+
36
+ describe Hexabat::MultiplePageRange do
37
+ it 'knows the first page' do
38
+ described_class.new(3, 5).first.should be 3
39
+ end
40
+
41
+ it 'knows the page count' do
42
+ described_class.new(1, 2).page_count.should be 2
43
+ described_class.new(5, 9).page_count.should be 5
44
+ end
45
+
46
+ it 'knows the last page' do
47
+ described_class.new(1, 5).last.should be 5
48
+ end
49
+
50
+ it 'knows the middle pages' do
51
+ described_class.new(1, 2).middle.should eq 0...0
52
+ described_class.new(1, 9).middle.should eq 2..8
53
+ end
54
+
55
+ it 'knows the middle pages count' do
56
+ described_class.new(1, 2).middle_page_count.should eq 0
57
+ described_class.new(1, 9).middle_page_count.should eq 7
58
+ end
59
+
60
+ it 'knows if it has more than one page' do
61
+ described_class.new(1, 2).multiple_pages?.should be_true
62
+ described_class.new(1, 5).multiple_pages?.should be_true
63
+ end
64
+ end
65
+
66
+ describe Hexabat::SinglePageRange do
67
+ subject { described_class.new page }
68
+ let(:page) { 2 }
69
+
70
+ it 'knows the first page' do
71
+ subject.first.should be 2
72
+ end
73
+
74
+ it 'knows the page count' do
75
+ subject.page_count.should be 1
76
+ end
77
+
78
+ it 'knows the last page' do
79
+ subject.last.should be 2
80
+ end
81
+
82
+ it 'knows the middle pages' do
83
+ subject.middle.should eq 0...0
84
+ end
85
+
86
+ it 'knows the middle pages count' do
87
+ subject.middle_page_count.should eq 0
88
+ end
89
+
90
+ it 'knows if it has more than one page' do
91
+ subject.multiple_pages?.should be_false
92
+ end
93
+ end
94
+
95
+ describe Hexabat::EmptyPageRange do
96
+ subject { described_class.new }
97
+
98
+ it 'knows the first page' do
99
+ subject.first.should be 0
100
+ end
101
+
102
+ it 'knows the page count' do
103
+ subject.page_count.should be 0
104
+ end
105
+
106
+ it 'knows the last page' do
107
+ subject.last.should be 0
108
+ end
109
+
110
+ it 'knows the middle pages' do
111
+ subject.middle.should eq 0...0
112
+ end
113
+
114
+ it 'knows the middle pages count' do
115
+ subject.middle_page_count.should eq 0
116
+ end
117
+
118
+ it 'knows if it has more than one page' do
119
+ subject.multiple_pages?.should be_false
120
+ end
121
+ end
@@ -0,0 +1,47 @@
1
+ require 'hexabat/page_request_builder'
2
+
3
+ describe 'Page request builders' do
4
+ let(:repository) { 'path11/hexabat' }
5
+ let(:response_processor) { mock :response_processor }
6
+
7
+ let(:http) { stub :http }
8
+ let(:get) { stub(:get).as_null_object }
9
+ let(:endpoint) { 'https://api.github.com/repos/path11/hexabat/issues' }
10
+ let(:query) { Hash[page: 1, per_page: 100, state: 'open'] }
11
+
12
+ before { EM::HttpRequest.stub(:new).with(endpoint).and_return(http) }
13
+
14
+ shared_examples_for 'a page request builder' do
15
+ it 'builds an EM::HttpRequest to retrieve the page' do
16
+ subject.for(page: 1, state: 'open').should eq get
17
+ end
18
+
19
+ it 'sets up the callback to process the response' do
20
+ page_callback = lambda{}
21
+ get.stub(:callback).and_yield(http)
22
+ response_processor.should_receive(:process).with(http, &page_callback)
23
+ subject.for(page: 1, state: 'open')
24
+ end
25
+ end
26
+
27
+ describe Hexabat::PageRequestBuilder::Unauthorized do
28
+ subject { Hexabat::PageRequestBuilder.for repository, response_processor }
29
+
30
+ before { http.stub(:get).with(query: query).and_return(get) }
31
+
32
+ it_behaves_like 'a page request builder'
33
+ end
34
+
35
+ describe Hexabat::PageRequestBuilder::TokenAuthorized do
36
+ subject { Hexabat::PageRequestBuilder.for repository, response_processor, token: token }
37
+ let(:token) { 'xxxx' }
38
+
39
+ before do
40
+ headers = { 'Authorization' => "token #{token}" }
41
+ http.stub(:get).with(query: query, head: headers).and_return(get)
42
+ end
43
+
44
+ it_behaves_like 'a page request builder'
45
+ end
46
+
47
+ end
@@ -0,0 +1,42 @@
1
+ require 'hexabat/page_response_processor'
2
+
3
+ describe Hexabat::PageResponseProcessor do
4
+ subject { described_class.new repository, callback, errback }
5
+ let(:repository) { 'path11/hexabat' }
6
+ let(:callback) { lambda{} }
7
+ let(:errback) { lambda{} }
8
+
9
+ let(:http) { stub(:http, response: issues, response_header: headers) }
10
+ let(:issues) { [:issue1, :issue2] }
11
+ let(:headers) { stub(:headers, status: 200) }
12
+
13
+ it 'yields every issue in the response to the callback' do
14
+ EM.stub(:next_tick).and_yield
15
+ callback.should_receive(:call).with(:issue1)
16
+ callback.should_receive(:call).with(:issue2)
17
+ subject.process(http)
18
+ end
19
+
20
+ it 'can be set with a callback for when the whole page is retrieved' do
21
+ page_range = stub(:page_range)
22
+ Hexabat::PageRange.stub(:from).with(headers).and_return(page_range)
23
+ page_callback = lambda{}
24
+ page_callback.should_receive(:call).with(page_range, issues.count)
25
+ subject.process(http, &page_callback)
26
+ end
27
+
28
+ it 'calls the errback if the repository is a fork' do
29
+ headers.stub(:status).and_return(410)
30
+ http.stub(:response).and_return('message' => 'Issues are disabled for this repo')
31
+ errback.should_receive(:call).with(repository, 410, 'Issues are disabled for this repo')
32
+ subject.process(http)
33
+ end
34
+
35
+ it 'calls the errback if the repository is a missing' do
36
+ headers.stub(:status).and_return(404)
37
+ http.stub(:response).and_return('message' => 'Not Found')
38
+ errback.should_receive(:call).with(repository, 404, 'Not Found')
39
+ subject.process(http)
40
+ end
41
+
42
+ end
@@ -0,0 +1,23 @@
1
+ require 'hexabat'
2
+
3
+ describe Hexabat do
4
+ let(:issue_retrieved) { lambda {|issue|} }
5
+ let(:issue_count_known) { lambda {|count|} }
6
+ let(:error_occurred) { lambda {|repository, status, message|} }
7
+
8
+ before do
9
+ Hexabat.on_issue_retrieved &issue_retrieved
10
+ Hexabat.on_issue_count_known &issue_count_known
11
+ Hexabat.on_error &error_occurred
12
+ end
13
+
14
+ it 'imports the repository' do
15
+ client = mock
16
+ Hexabat::Client.stub(:new).with('path11/hexabat', {}).and_return(client)
17
+ client.should_receive(:on).with(issue_retrieved: issue_retrieved)
18
+ client.should_receive(:on).with(issue_count_known: issue_count_known)
19
+ client.should_receive(:on).with(error: error_occurred)
20
+ client.should_receive(:import)
21
+ Hexabat.import 'path11/hexabat'
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,211 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hexabat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - path11
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: em-http-request
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: yajl-ruby
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: cucumber
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: webmock
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: A Github issues importing tool
127
+ email:
128
+ - alberto@path11.com
129
+ - ecomba@path11.com
130
+ - javier@path11.com
131
+ - sebastian@path11.com
132
+ executables: []
133
+ extensions: []
134
+ extra_rdoc_files: []
135
+ files:
136
+ - .gitignore
137
+ - .rvmrc
138
+ - .travis.yml
139
+ - Gemfile
140
+ - LICENSE
141
+ - README.md
142
+ - Rakefile
143
+ - features/callbacks.feature
144
+ - features/fixtures/100_open_issues.rb
145
+ - features/fixtures/300_closed_issues.rb
146
+ - features/fixtures/single_open_issue.rb
147
+ - features/step_definitions/callbacks.rb
148
+ - features/support/env.rb
149
+ - hexabat.gemspec
150
+ - lib/hexabat.rb
151
+ - lib/hexabat/client.rb
152
+ - lib/hexabat/importer.rb
153
+ - lib/hexabat/issue_count.rb
154
+ - lib/hexabat/page_range.rb
155
+ - lib/hexabat/page_request_builder.rb
156
+ - lib/hexabat/page_response_processor.rb
157
+ - lib/hexabat/version.rb
158
+ - spec/hexabat/client_spec.rb
159
+ - spec/hexabat/importer_spec.rb
160
+ - spec/hexabat/issue_count_spec.rb
161
+ - spec/hexabat/page_range_spec.rb
162
+ - spec/hexabat/page_request_buider_spec.rb
163
+ - spec/hexabat/page_response_processor_spec.rb
164
+ - spec/hexabat_spec.rb
165
+ homepage: ''
166
+ licenses: []
167
+ post_install_message:
168
+ rdoc_options: []
169
+ require_paths:
170
+ - lib
171
+ required_ruby_version: !ruby/object:Gem::Requirement
172
+ none: false
173
+ requirements:
174
+ - - ! '>='
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ segments:
178
+ - 0
179
+ hash: 2707408037992863866
180
+ required_rubygems_version: !ruby/object:Gem::Requirement
181
+ none: false
182
+ requirements:
183
+ - - ! '>='
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ segments:
187
+ - 0
188
+ hash: 2707408037992863866
189
+ requirements: []
190
+ rubyforge_project:
191
+ rubygems_version: 1.8.24
192
+ signing_key:
193
+ specification_version: 3
194
+ summary: ! 'Importing all the issues of a Github repository is a complex task: the
195
+ Github API does not provide an easy way of doing it. Hexabat will help you with
196
+ that. It will allow you to find out the total number of issues (counting both open
197
+ and closed ones) and to perform an action with each one of them.'
198
+ test_files:
199
+ - features/callbacks.feature
200
+ - features/fixtures/100_open_issues.rb
201
+ - features/fixtures/300_closed_issues.rb
202
+ - features/fixtures/single_open_issue.rb
203
+ - features/step_definitions/callbacks.rb
204
+ - features/support/env.rb
205
+ - spec/hexabat/client_spec.rb
206
+ - spec/hexabat/importer_spec.rb
207
+ - spec/hexabat/issue_count_spec.rb
208
+ - spec/hexabat/page_range_spec.rb
209
+ - spec/hexabat/page_request_buider_spec.rb
210
+ - spec/hexabat/page_response_processor_spec.rb
211
+ - spec/hexabat_spec.rb