dash-mario 0.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -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