dash-mario 0.15

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.
@@ -0,0 +1,251 @@
1
+ require_relative "setup"
2
+
3
+ test DashFu::Mario::Github do
4
+ context "setup" do
5
+ setup { setup_source "repo"=>"assaf/vanity", "branch"=>"master" }
6
+
7
+ should "default branch to master" do
8
+ setup_source "repo"=>"assaf/vanity", "branch"=>" "
9
+ assert_equal "master", source["branch"]
10
+ end
11
+
12
+ context "metric" do
13
+ subject { metric }
14
+
15
+ should "use repository name" do
16
+ assert_equal "Github: assaf/vanity", subject.name
17
+ end
18
+
19
+ should "measure totals" do
20
+ assert subject.totals
21
+ end
22
+
23
+ should "capture commits" do
24
+ assert subject.columns.include?(:id=>"commits", :label=>"Commits")
25
+ end
26
+
27
+ should "capture watchers" do
28
+ assert subject.columns.include?(:id=>"watchers", :label=>"Watchers")
29
+ end
30
+
31
+ should "capture forks" do
32
+ assert subject.columns.include?(:id=>"forks", :label=>"Forks")
33
+ end
34
+ end
35
+ end
36
+
37
+
38
+ context "validation" do
39
+ should "raise error if repository name missing" do
40
+ assert_raise(RuntimeError) { setup_source "repo"=>" ", "branch"=>"master" }
41
+ end
42
+
43
+ should "raise error if repository name not user/repo" do
44
+ assert_raise(RuntimeError) { setup_source "repo"=>"vanity", "branch"=>"master" }
45
+ end
46
+
47
+ should "allow alphanumeric, minus and underscore" do
48
+ setup_source "repo"=>"assaf/the-vanity_0", "branch"=>"master"
49
+ assert metric.valid?
50
+ end
51
+
52
+ should "create valid metric" do
53
+ setup_source "repo"=>"assaf/vanity", "branch"=>"master"
54
+ assert metric.valid?
55
+ end
56
+ end
57
+
58
+
59
+ context "update" do
60
+ setup { setup_source "repo"=>"assaf/vanity", "branch"=>"master" }
61
+
62
+ should "handle 404" do
63
+ stub_request(:get, interactions.first.uri).to_return :status=>404
64
+ assert_raise(RuntimeError) { update_source }
65
+ assert_equal "Could not find the repository assaf/vanity", last_error
66
+ end
67
+
68
+ should "handle 401" do
69
+ stub_request(:get, interactions.first.uri).to_return :status=>401
70
+ assert_raise(RuntimeError) { update_source }
71
+ assert_equal "You are not authorized to access this repository, or invalid username/password", last_error
72
+ end
73
+
74
+ should "handle other error" do
75
+ stub_request(:get, interactions.first.uri).to_return :status=>500
76
+ assert_raise(RuntimeError) { update_source }
77
+ assert_equal "Last request didn't go as expected, trying again later", last_error
78
+ end
79
+
80
+ should "handle invlid document entity" do
81
+ stub_request(:get, interactions.first.uri).to_return :body=>"Not JSON"
82
+ assert_raise(RuntimeError) { update_source }
83
+ assert_equal "Last request didn't go as expected, trying again later", last_error
84
+ end
85
+
86
+ should "capture number of commits" do
87
+ update_source
88
+ assert_equal 35, totals[:commits]
89
+ end
90
+
91
+ should "capture number of wacthers" do
92
+ update_source
93
+ assert_equal 534, totals[:watchers]
94
+ end
95
+
96
+ should "capture number of forks" do
97
+ update_source
98
+ assert_equal 36, totals[:forks]
99
+ end
100
+
101
+ should "not create any activity" do
102
+ update_source
103
+ assert activities.empty?
104
+ end
105
+
106
+ context "repeating" do
107
+ setup do
108
+ update_source
109
+ repo = interactions.select { |i| i.uri =~ /repos\/show/ }
110
+ stub_request(:get, repo.first.uri).to_return :body=>repo.last.response.body
111
+ commits = interactions.select { |i| i.uri =~ /commits\/list/ }
112
+ stub_request(:get, commits.first.uri).to_return :body=>commits.last.response.body
113
+ update_source
114
+ end
115
+
116
+ should "update watchers" do
117
+ assert_equal 555, totals[:watchers]
118
+ end
119
+
120
+ should "update forks" do
121
+ assert_equal 38, totals[:forks]
122
+ end
123
+
124
+ should "capture new number of commits" do
125
+ assert_equal 39, totals[:commits]
126
+ end
127
+
128
+ context "activity" do
129
+ subject { activity }
130
+
131
+ should "capture commit URL" do
132
+ assert_equal "http://github.com/assaf/vanity/commit/dd154a9fdd2ac534b62b55b8acac2fd092d65439", subject.url
133
+ end
134
+
135
+ should "capture commit SHA" do
136
+ assert_equal "cc156a9fdd2ac534b62b55b8acac2fd092d65439", subject.uid
137
+ end
138
+
139
+ should "capture timestamp" do
140
+ assert_equal Time.parse("2010-08-06T00:22:01-07:00 UTC"), subject.timestamp
141
+ end
142
+
143
+ should "capture push" do
144
+ assert_match %{pushed to master at <a href="http://github.com/assaf/vanity">assaf/vanity</a>:}, subject.html
145
+ end
146
+
147
+ should "tag as push" do
148
+ assert_contains subject.tags, "push"
149
+ end
150
+
151
+ should "be valid" do
152
+ subject.validate
153
+ assert subject.valid?
154
+ end
155
+
156
+ context "person" do
157
+ subject { activity.person }
158
+
159
+ should "capture full name" do
160
+ assert_equal "Assaf Arkin", subject.fullname
161
+ end
162
+
163
+ should "capture email" do
164
+ assert_equal "assaf@labnotes.org", subject.email
165
+ end
166
+
167
+ should "capture identity" do
168
+ assert_contains subject.identities, "github.com:assaf"
169
+ end
170
+ end
171
+
172
+ context "commit message" do
173
+ subject { (Nokogiri::HTML(activity.html)/"blockquote").inner_text.strip }
174
+
175
+ should "start with commit SHA" do
176
+ assert_match /^cc156a9 /, subject
177
+ end
178
+
179
+ should "include first 50 characters of message" do
180
+ with_message "This is a very long message and we're only going to show the first 50 characters of it."
181
+ assert_equal 50, subject[/\s(.*)/, 1].length
182
+ end
183
+
184
+ should "use only first line of message" do
185
+ with_message "This message is made\nof two lines"
186
+ assert_match "This message is made", subject[/\s(.*)/, 1]
187
+ end
188
+
189
+ def with_message(message)
190
+ commits = interactions.select { |i| i.uri =~ /commits\/list/ }
191
+ commit = JSON.parse(commits.first.response.body)["commits"].first
192
+ commit["message"] = message
193
+ stub_request(:get, commits.first.uri).to_return :body=>{ :commits=>[commit] }.to_json
194
+ update_source
195
+ end
196
+ end
197
+
198
+ context "sequential commits" do
199
+ setup do
200
+ interaction = interactions.select { |i| i.uri =~ /commits\/list/ }.last
201
+ stub_request(:get, interaction.uri).to_return :body=>interaction.response.body
202
+ update_source
203
+ end
204
+ subject { (Nokogiri::HTML(activity.html)/"blockquote") }
205
+
206
+ should "show as multiple activities" do
207
+ assert_equal 3, activities.count # 4 commits -> 3 activities
208
+ end
209
+
210
+ should "merge related commits into single activity" do
211
+ assert_equal 2, subject.length # last one has two commits
212
+ end
213
+
214
+ should "merge related commits in order they were listed" do
215
+ first, second = subject.map { |bq| bq.inner_text.strip }
216
+ assert_equal "cc156a9 Most recent commit", first
217
+ assert_equal "dd156a9 Not most recent commit", second
218
+ end
219
+ end
220
+ end
221
+
222
+ end
223
+ end
224
+
225
+
226
+ context "meta" do
227
+ setup { setup_source "repo"=>"assaf/vanity", "branch"=>"master" }
228
+ subject { meta }
229
+
230
+ should "link to repository" do
231
+ assert_contains subject, :title=>"Repository", :text=>"assaf/vanity", :url=>"http://github.com/assaf/vanity"
232
+ end
233
+
234
+ should "include project description" do
235
+ assert_contains subject, :text=>"Experiment Driven Development for Ruby"
236
+ end
237
+
238
+ should "link to project home page" do
239
+ assert_contains subject, :title=>"Home page", :url=>"http://vanity.labnotes.org"
240
+ end
241
+
242
+ should "list branch name" do
243
+ assert_contains subject, :title=>"Branch", :text=>"master"
244
+ end
245
+
246
+ should "show last commit message" do
247
+ assert_contains subject, :title=>"Commit", :text=>"Gemfile changes necessary to pass test suite.",
248
+ :url=>"http://github.com/assaf/vanity/commit/dd154a9fdd2ac534b62b55b8acac2fd092d6543a"
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,21 @@
1
+ class Activity
2
+ def initialize(args = {})
3
+ args.each do |name, value|
4
+ send "#{name}=", value
5
+ end
6
+ end
7
+
8
+ attr_accessor :uid, :html, :text, :url, :timestamp, :tags, :person
9
+
10
+ def valid?
11
+ validate rescue return false
12
+ return true
13
+ end
14
+
15
+ def validate
16
+ raise "Must specify html or text" if html.blank? && text.blank?
17
+ raise "Tags must be alphanumeric, underscores allowed" unless tags.nil? || tags.all? { |tag| tag =~ /^[\w_]+$/ }
18
+ person.validate if person
19
+ end
20
+
21
+ end
@@ -0,0 +1,22 @@
1
+ class Metric
2
+ def initialize(args = {})
3
+ args.each do |name, value|
4
+ send "#{name}=", value
5
+ end
6
+ end
7
+
8
+ attr_accessor :name, :columns, :totals
9
+
10
+ def valid?
11
+ validate rescue return false
12
+ return true
13
+ end
14
+
15
+ def validate
16
+ fail "Metric name missing or a blank string" if name.blank?
17
+ fail "Metric must have at least one column" if columns.empty?
18
+ col_ids = columns.map { |col| col[:id] }
19
+ fail "All metric columns must have an id" unless col_ids.all? { |col_id| col_id }
20
+ fail "Metric columns must have unique ids" unless col_ids.uniq == col_ids
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ class Person
2
+
3
+ def initialize(args = {})
4
+ args[:identities] = Array.wrap(args[:identities])
5
+ args.each do |name, value|
6
+ send "#{name}=", value
7
+ end
8
+ end
9
+
10
+ attr_accessor :fullname, :email, :identities, :photo_url
11
+
12
+ def valid?
13
+ validate rescue return false
14
+ return true
15
+ end
16
+
17
+ def validate
18
+ if photo_url
19
+ uri = URI.parse(photo_url)
20
+ fail "Photo URL must be absolute HTTP URL" unless uri.scheme == "http" && uri.absolute?
21
+ end
22
+ fail "Identities must be of the form name:value" unless identities.nil? || identities.all? { |id| id =~ /^[\w\.]+:.+$/ }
23
+ end
24
+
25
+ end
@@ -0,0 +1,161 @@
1
+ # class, includes the modules DashFu::Mario::TestHelpers and Webmock, sets the
2
+ # Mario class, and other nicities.
3
+ #
4
+ # @example
5
+ # test DashFu::Mario::RubyGems do
6
+ # . . .
7
+ # end
8
+ def test(klass, &block)
9
+ Class.new(Test::Unit::TestCase).tap do |test_case|
10
+ test_case.class_eval do
11
+ include WebMock
12
+ include DashFu::Mario::TestHelpers
13
+ end
14
+ test_case.instance_variable_set :@mario_class, klass
15
+ test_case.class_eval &block
16
+ Object.const_set "#{klass.name.demodulize}Test", test_case
17
+ end
18
+ end
19
+
20
+
21
+ module DashFu
22
+ module Mario
23
+ module TestHelpers
24
+
25
+ # Default setup. If you override setup, remember to call super.
26
+ def setup
27
+ WebMock.reset_webmock
28
+ VCR.insert_cassette(mario_id)
29
+ end
30
+
31
+ # Default teardown. If you override teardown, remember to call super.
32
+ def teardown
33
+ VCR.eject_cassette
34
+ end
35
+
36
+ # Returns the Mario. New Mario instance for each test.
37
+ def mario
38
+ @mario ||= mario_class.new
39
+ end
40
+
41
+ # Sets up source using supplied parameters, also calls validate.
42
+ #
43
+ # @example
44
+ # assert_raise RuntimeError do
45
+ # setup_source "gem_name"=>""
46
+ # end
47
+ # @example
48
+ # setup { setup_source "gem_name"=>"vanity" }
49
+ def setup_source(params)
50
+ mario.setup source, params
51
+ mario.validate source
52
+ end
53
+
54
+ # Returns the source. New source for each test.
55
+ def source
56
+ @source ||= {}
57
+ end
58
+
59
+ # Updates the source. If an error occurs, raises an exception. You can get
60
+ # the last error message by calling #last_error.
61
+ def update_source(request = nil)
62
+ @last_error = nil
63
+ mario.update source, request, &updator
64
+ rescue
65
+ @last_error = $!.message
66
+ raise
67
+ end
68
+
69
+ # Last error after calling #update_source.
70
+ attr_reader :last_error
71
+
72
+ # Returns a new metric. See Metric.
73
+ #
74
+ # @example
75
+ # assert_equal "My metric", metric.name
76
+ def metric
77
+ values = { :name=>source["metric.name"], :columns=>source["metric.columns"], :totals=>!!source["metric.totals"] }
78
+ Metric.new(values)
79
+ end
80
+
81
+ # Performs an update on the source and returns the meta-data.
82
+ #
83
+ # @example
84
+ # assert meta.include?(:title=>"Version", :text=>"1.4.0")
85
+ def meta
86
+ mario.update source, nil, &updator
87
+ mario.meta(source)
88
+ end
89
+
90
+ # Returns totals collected by this source.
91
+ #
92
+ # @example
93
+ # assert_equal({ :hits=>54 }, totals)
94
+ def totals
95
+ @totals ||= {}
96
+ end
97
+
98
+ # Returns all activities reported by this source. See Activity.
99
+ def activities
100
+ @activities ||= []
101
+ end
102
+
103
+ # Returns last (most recent) activity.
104
+ #
105
+ # @example
106
+ # assert_equal "Pushed a new release of awesome", activity.body
107
+ def activity
108
+ activities.last
109
+ end
110
+
111
+ # Returns the named fixture. Fixture looked up in the file
112
+ # test/fixtures/<mario_id>.rb
113
+ def fixture(name)
114
+ fixtures = self.class.instance_variable_get(:@fixtures)
115
+ unless fixtures
116
+ fixtures = YAML.load_file(File.dirname(__FILE__) + "/fixtures/#{mario_id}.yml")
117
+ self.class.instance_variable_set :@fixtures, fixtures
118
+ end
119
+ fixtures[name]
120
+ end
121
+
122
+ # Returns the VCR interactions.
123
+ def interactions
124
+ VCR.current_cassette.recorded_interactions
125
+ end
126
+
127
+ protected
128
+
129
+ # Mario identifier (e.g. RubyGems => ruby_gems).
130
+ def mario_id
131
+ @mario_id ||= mario_class.name.demodulize.underscore
132
+ end
133
+
134
+ # The Mario class.
135
+ def mario_class
136
+ @mario_class ||= self.class.instance_variable_get :@mario_class
137
+ end
138
+
139
+ # Source update block.
140
+ def updator
141
+ lambda do |args|
142
+ if args[:set]
143
+ totals.update args[:set]
144
+ elsif args[:inc]
145
+ args[:inc].each do |name, value|
146
+ totals[name] ||= 0
147
+ totals[name] += value
148
+ end
149
+ elsif args[:activity]
150
+ values = args[:activity].clone
151
+ if person = values.delete(:person)
152
+ values[:person] = Person.new(person)
153
+ end
154
+ activities.push Activity.new(values)
155
+ end
156
+ end
157
+ end
158
+
159
+ end
160
+ end
161
+ end