codeclimate-services 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +7 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +121 -0
  8. data/Rakefile +11 -0
  9. data/bin/bundler +16 -0
  10. data/bin/coderay +16 -0
  11. data/bin/nokogiri +16 -0
  12. data/bin/pry +16 -0
  13. data/bin/rake +16 -0
  14. data/codeclimate-services.gemspec +29 -0
  15. data/config/cacert.pem +4026 -0
  16. data/config/load.rb +4 -0
  17. data/lib/axiom/types/password.rb +7 -0
  18. data/lib/cc/formatters/linked_formatter.rb +60 -0
  19. data/lib/cc/formatters/plain_formatter.rb +36 -0
  20. data/lib/cc/formatters/snapshot_formatter.rb +101 -0
  21. data/lib/cc/formatters/ticket_formatter.rb +27 -0
  22. data/lib/cc/helpers/coverage_helper.rb +25 -0
  23. data/lib/cc/helpers/issue_helper.rb +9 -0
  24. data/lib/cc/helpers/quality_helper.rb +44 -0
  25. data/lib/cc/helpers/vulnerability_helper.rb +31 -0
  26. data/lib/cc/presenters/github_pull_requests_presenter.rb +54 -0
  27. data/lib/cc/service/config.rb +4 -0
  28. data/lib/cc/service/formatter.rb +34 -0
  29. data/lib/cc/service/helper.rb +54 -0
  30. data/lib/cc/service/http.rb +87 -0
  31. data/lib/cc/service/invocation/invocation_chain.rb +15 -0
  32. data/lib/cc/service/invocation/with_error_handling.rb +45 -0
  33. data/lib/cc/service/invocation/with_metrics.rb +37 -0
  34. data/lib/cc/service/invocation/with_retries.rb +17 -0
  35. data/lib/cc/service/invocation/with_return_values.rb +18 -0
  36. data/lib/cc/service/invocation.rb +57 -0
  37. data/lib/cc/service/response_check.rb +42 -0
  38. data/lib/cc/service.rb +127 -0
  39. data/lib/cc/services/asana.rb +90 -0
  40. data/lib/cc/services/campfire.rb +55 -0
  41. data/lib/cc/services/flowdock.rb +61 -0
  42. data/lib/cc/services/github_issues.rb +80 -0
  43. data/lib/cc/services/github_pull_requests.rb +210 -0
  44. data/lib/cc/services/hipchat.rb +57 -0
  45. data/lib/cc/services/jira.rb +93 -0
  46. data/lib/cc/services/lighthouse.rb +79 -0
  47. data/lib/cc/services/pivotal_tracker.rb +78 -0
  48. data/lib/cc/services/slack.rb +124 -0
  49. data/lib/cc/services/version.rb +5 -0
  50. data/lib/cc/services.rb +9 -0
  51. data/pull_request_test.rb +47 -0
  52. data/service_test.rb +86 -0
  53. data/test/asana_test.rb +85 -0
  54. data/test/axiom/types/password_test.rb +22 -0
  55. data/test/campfire_test.rb +144 -0
  56. data/test/fixtures.rb +68 -0
  57. data/test/flowdock_test.rb +148 -0
  58. data/test/formatters/snapshot_formatter_test.rb +47 -0
  59. data/test/github_issues_test.rb +96 -0
  60. data/test/github_pull_requests_test.rb +293 -0
  61. data/test/helper.rb +50 -0
  62. data/test/hipchat_test.rb +130 -0
  63. data/test/invocation_error_handling_test.rb +51 -0
  64. data/test/invocation_return_values_test.rb +21 -0
  65. data/test/invocation_test.rb +167 -0
  66. data/test/jira_test.rb +80 -0
  67. data/test/lighthouse_test.rb +74 -0
  68. data/test/pivotal_tracker_test.rb +73 -0
  69. data/test/presenters/github_pull_requests_presenter_test.rb +49 -0
  70. data/test/service_test.rb +63 -0
  71. data/test/slack_test.rb +222 -0
  72. data/test/support/fake_logger.rb +11 -0
  73. data/test/with_metrics_test.rb +19 -0
  74. metadata +263 -0
@@ -0,0 +1,293 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class TestGitHubPullRequests < CC::Service::TestCase
4
+ def test_pull_request_status_pending
5
+ expect_status_update("pbrisbin/foo", "abc123", {
6
+ "state" => "pending",
7
+ "description" => /is analyzing/,
8
+ })
9
+
10
+ receive_pull_request({ update_status: true }, {
11
+ github_slug: "pbrisbin/foo",
12
+ commit_sha: "abc123",
13
+ state: "pending",
14
+ })
15
+ end
16
+
17
+ def test_pull_request_status_success_detailed
18
+ expect_status_update("pbrisbin/foo", "abc123", {
19
+ "state" => "success",
20
+ "description" => "Code Climate found 2 new issues and 1 fixed issue.",
21
+ })
22
+
23
+ receive_pull_request(
24
+ { update_status: true },
25
+ {
26
+ github_slug: "pbrisbin/foo",
27
+ commit_sha: "abc123",
28
+ state: "success"
29
+ }
30
+ )
31
+ end
32
+
33
+ def test_pull_request_status_failure
34
+ expect_status_update("pbrisbin/foo", "abc123", {
35
+ "state" => "failure",
36
+ "description" => "Code Climate found 2 new issues and 1 fixed issue.",
37
+ })
38
+
39
+ receive_pull_request(
40
+ { update_status: true },
41
+ {
42
+ github_slug: "pbrisbin/foo",
43
+ commit_sha: "abc123",
44
+ state: "failure"
45
+ }
46
+ )
47
+ end
48
+
49
+ def test_pull_request_status_success_generic
50
+ expect_status_update("pbrisbin/foo", "abc123", {
51
+ "state" => "success",
52
+ "description" => /found 2 new issues and 1 fixed issue/,
53
+ })
54
+
55
+ receive_pull_request({ update_status: true }, {
56
+ github_slug: "pbrisbin/foo",
57
+ commit_sha: "abc123",
58
+ state: "success",
59
+ })
60
+ end
61
+
62
+ def test_pull_request_status_error
63
+ expect_status_update("pbrisbin/foo", "abc123", {
64
+ "state" => "error",
65
+ "description" => CC::Service::GitHubPullRequests::DEFAULT_ERROR,
66
+ })
67
+
68
+ receive_pull_request({ update_status: true }, {
69
+ github_slug: "pbrisbin/foo",
70
+ commit_sha: "abc123",
71
+ state: "error",
72
+ message: nil,
73
+ })
74
+ end
75
+
76
+ def test_pull_request_status_error_message_provided
77
+ expect_status_update("pbrisbin/foo", "abc123", {
78
+ "state" => "error",
79
+ "description" => "descriptive message",
80
+ })
81
+
82
+ receive_pull_request({ update_status: true }, {
83
+ github_slug: "pbrisbin/foo",
84
+ commit_sha: "abc123",
85
+ state: "error",
86
+ message: "descriptive message",
87
+ })
88
+ end
89
+
90
+ def test_pull_request_status_skipped
91
+ expect_status_update("pbrisbin/foo", "abc123", {
92
+ "state" => "success",
93
+ "description" => /skipped analysis/,
94
+ })
95
+
96
+ receive_pull_request({ update_status: true }, {
97
+ github_slug: "pbrisbin/foo",
98
+ commit_sha: "abc123",
99
+ state: "skipped",
100
+ })
101
+ end
102
+
103
+ def test_no_status_update_for_skips_when_update_status_config_is_falsey
104
+ # With no POST expectation, test will fail if request is made.
105
+
106
+ receive_pull_request({}, {
107
+ github_slug: "pbrisbin/foo",
108
+ commit_sha: "abc123",
109
+ state: "skipped",
110
+ })
111
+ end
112
+
113
+ def test_no_status_update_for_pending_when_update_status_config_is_falsey
114
+ # With no POST expectation, test will fail if request is made.
115
+
116
+ receive_pull_request({}, {
117
+ github_slug: "pbrisbin/foo",
118
+ commit_sha: "abc123",
119
+ state: "pending",
120
+ })
121
+ end
122
+
123
+ def test_no_status_update_for_error_when_update_status_config_is_falsey
124
+ # With no POST expectation, test will fail if request is made.
125
+
126
+ receive_pull_request({}, {
127
+ github_slug: "pbrisbin/foo",
128
+ commit_sha: "abc123",
129
+ state: "error",
130
+ message: nil,
131
+ })
132
+ end
133
+
134
+
135
+ def test_no_comment_for_skips_regardless_of_add_comment_config
136
+ # With no POST expectation, test will fail if request is made.
137
+
138
+ receive_pull_request({ add_comment: true }, {
139
+ github_slug: "pbrisbin/foo",
140
+ commit_sha: "abc123",
141
+ state: "skipped",
142
+ })
143
+ end
144
+
145
+ def test_pull_request_status_test_success
146
+ @stubs.post("/repos/pbrisbin/foo/statuses/#{"0" * 40}") { |env| [422, {}, ""] }
147
+
148
+ assert receive_test({ update_status: true }, { github_slug: "pbrisbin/foo" })[:ok], "Expected test of pull request to be true"
149
+ end
150
+
151
+ def test_pull_request_status_test_failure
152
+ @stubs.post("/repos/pbrisbin/foo/statuses/#{"0" * 40}") { |env| [401, {}, ""] }
153
+
154
+ assert_raises(CC::Service::HTTPError) do
155
+ receive_test({ update_status: true }, { github_slug: "pbrisbin/foo" })
156
+ end
157
+ end
158
+
159
+ def test_pull_request_comment_test_success
160
+ @stubs.get("/user") { |env| [200, { "x-oauth-scopes" => "gist, user, repo" }, ""] }
161
+
162
+ assert receive_test({ add_comment: true })[:ok], "Expected test of pull request to be true"
163
+ end
164
+
165
+ def test_pull_request_comment_test_failure_insufficient_permissions
166
+ @stubs.get("/user") { |env| [200, { "x-oauth-scopes" => "gist, user" }, ""] }
167
+
168
+ assert !receive_test({ add_comment: true })[:ok], "Expected failed test of pull request"
169
+ end
170
+
171
+ def test_pull_request_comment_test_failure_bad_token
172
+ @stubs.get("/user") { |env| [401, {}, ""] }
173
+
174
+ assert !receive_test({ add_comment: true })[:ok], "Expected failed test of pull request"
175
+ end
176
+
177
+ def test_pull_request_failure_on_status_requesting_both
178
+ @stubs.post("/repos/pbrisbin/foo/statuses/#{"0" * 40}") { |env| [401, {}, ""] }
179
+
180
+ assert_raises(CC::Service::HTTPError) do
181
+ receive_test({ update_status: true, add_comment: true }, { github_slug: "pbrisbin/foo" })
182
+ end
183
+ end
184
+
185
+ def test_pull_request_failure_on_comment_requesting_both
186
+ @stubs.post("/repos/pbrisbin/foo/statuses/#{"0" * 40}") { |env| [422, {}, ""] }
187
+ @stubs.get("/user") { |env| [401, { "x-oauth-scopes" => "gist, user, repo" }, ""] }
188
+
189
+ assert_false receive_test({ update_status: true, add_comment: true }, { github_slug: "pbrisbin/foo" })[:ok]
190
+ end
191
+
192
+ def test_pull_request_success_both
193
+ @stubs.post("/repos/pbrisbin/foo/statuses/#{"0" * 40}") { |env| [422, {}, ""] }
194
+ @stubs.get("/user") { |env| [200, { "x-oauth-scopes" => "gist, user, repo" }, ""] }
195
+
196
+ assert receive_test({ update_status: true, add_comment: true }, { github_slug: "pbrisbin/foo" })[:ok], "Expected test of pull request to be true"
197
+ end
198
+
199
+ def test_pull_request_comment
200
+ stub_existing_comments("pbrisbin/foo", 1, %w[Hey Yo])
201
+
202
+ expect_comment("pbrisbin/foo", 1, %r{href="http://example.com">analyzed})
203
+
204
+ receive_pull_request({ add_comment: true }, {
205
+ github_slug: "pbrisbin/foo",
206
+ number: 1,
207
+ state: "success",
208
+ compare_url: "http://example.com",
209
+ issue_comparison_counts: {
210
+ "fixed" => 2,
211
+ "new" => 1,
212
+ }
213
+ })
214
+ end
215
+
216
+ def test_pull_request_comment_already_present
217
+ stub_existing_comments("pbrisbin/foo", 1, [
218
+ '<b>Code Climate</b> has <a href="">analyzed this pull request</a>'
219
+ ])
220
+
221
+ # With no POST expectation, test will fail if request is made.
222
+
223
+ response = receive_pull_request({
224
+ add_comment: true,
225
+ update_status: false
226
+ }, {
227
+ github_slug: "pbrisbin/foo",
228
+ number: 1,
229
+ state: "success",
230
+ })
231
+
232
+ assert_equal({ ok: true, message: "Comment already present" }, response)
233
+ end
234
+
235
+ def test_pull_request_unknown_state
236
+ response = receive_pull_request({}, { state: "unknown" })
237
+
238
+ assert_equal({ ok: false, message: "Unknown state" }, response)
239
+ end
240
+
241
+ def test_pull_request_nothing_happened
242
+ response = receive_pull_request({}, { state: "success" })
243
+
244
+ assert_equal({ ok: false, message: "Nothing happened" }, response)
245
+ end
246
+
247
+ private
248
+
249
+ def expect_status_update(repo, commit_sha, params)
250
+ @stubs.post "repos/#{repo}/statuses/#{commit_sha}" do |env|
251
+ assert_equal "token 123", env[:request_headers]["Authorization"]
252
+
253
+ body = JSON.parse(env[:body])
254
+
255
+ params.each do |k, v|
256
+ assert v === body[k],
257
+ "Unexpected value for #{k}. #{v.inspect} !== #{body[k].inspect}"
258
+ end
259
+ end
260
+ end
261
+
262
+ def stub_existing_comments(repo, number, bodies)
263
+ body = bodies.map { |b| { body: b } }.to_json
264
+
265
+ @stubs.get("repos/#{repo}/issues/#{number}/comments") { [200, {}, body] }
266
+ end
267
+
268
+ def expect_comment(repo, number, content)
269
+ @stubs.post "repos/#{repo}/issues/#{number}/comments" do |env|
270
+ body = JSON.parse(env[:body])
271
+ assert_equal "token 123", env[:request_headers]["Authorization"]
272
+ assert content === body["body"],
273
+ "Unexpected comment body. #{content.inspect} !== #{body["body"].inspect}"
274
+ [200, {}, '{"id": 2}']
275
+ end
276
+ end
277
+
278
+ def receive_pull_request(config, event_data)
279
+ receive(
280
+ CC::Service::GitHubPullRequests,
281
+ { oauth_token: "123" }.merge(config),
282
+ { name: "pull_request", issue_comparison_counts: {'fixed' => 1, 'new' => 2} }.merge(event_data)
283
+ )
284
+ end
285
+
286
+ def receive_test(config, event_data = {})
287
+ receive(
288
+ CC::Service::GitHubPullRequests,
289
+ { oauth_token: "123" }.merge(config),
290
+ { name: "test", issue_comparison_counts: {'fixed' => 1, 'new' => 2} }.merge(event_data)
291
+ )
292
+ end
293
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,50 @@
1
+ require 'test/unit'
2
+ require 'mocha/test_unit'
3
+ require 'pp'
4
+
5
+ require "codeclimate-test-reporter"
6
+ CodeClimate::TestReporter.start
7
+
8
+ cwd = File.expand_path(File.dirname(__FILE__))
9
+ require "#{cwd}/../config/load"
10
+ require "#{cwd}/fixtures"
11
+ Dir["#{cwd}/support/*.rb"].sort.each do |helper|
12
+ require helper
13
+ end
14
+ CC::Service.load_services
15
+
16
+
17
+ class CC::Service::TestCase < Test::Unit::TestCase
18
+ def setup
19
+ @stubs = Faraday::Adapter::Test::Stubs.new
20
+
21
+ I18n.enforce_available_locales = true
22
+ end
23
+
24
+ def teardown
25
+ @stubs.verify_stubbed_calls
26
+ end
27
+
28
+ def service(klass, data, payload)
29
+ service = klass.new(data, payload)
30
+ service.http :adapter => [:test, @stubs]
31
+ service
32
+ end
33
+
34
+ def receive(*args)
35
+ service(*args).receive
36
+ end
37
+
38
+ def service_post(*args)
39
+ service(
40
+ CC::Service,
41
+ { data: "my data" },
42
+ event(:quality, to: "D", from: "C")
43
+ ).service_post(*args)
44
+ end
45
+
46
+ def stub_http(url, response = nil, &block)
47
+ block ||= lambda{|*args| response }
48
+ @stubs.post(url, &block)
49
+ end
50
+ end
@@ -0,0 +1,130 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class TestHipChat < CC::Service::TestCase
4
+ def test_test_hook
5
+ assert_hipchat_receives(
6
+ "green",
7
+ { name: "test", repo_name: "Rails" },
8
+ "[Rails] This is a test of the HipChat service hook"
9
+ )
10
+ end
11
+
12
+ def test_coverage_improved
13
+ e = event(:coverage, to: 90.2, from: 80)
14
+
15
+ assert_hipchat_receives("green", e, [
16
+ "[Example]",
17
+ "<a href=\"https://codeclimate.com/repos/1/feed\">Test coverage</a>",
18
+ "has improved to 90.2% (+10.2%)",
19
+ "(<a href=\"https://codeclimate.com/repos/1/compare\">Compare</a>)"
20
+ ].join(" "))
21
+ end
22
+
23
+ def test_coverage_declined
24
+ e = event(:coverage, to: 88.6, from: 94.6)
25
+
26
+ assert_hipchat_receives("red", e, [
27
+ "[Example]",
28
+ "<a href=\"https://codeclimate.com/repos/1/feed\">Test coverage</a>",
29
+ "has declined to 88.6% (-6.0%)",
30
+ "(<a href=\"https://codeclimate.com/repos/1/compare\">Compare</a>)"
31
+ ].join(" "))
32
+ end
33
+
34
+ def test_quality_improved
35
+ e = event(:quality, to: "A", from: "B")
36
+
37
+ assert_hipchat_receives("green", e, [
38
+ "[Example]",
39
+ "<a href=\"https://codeclimate.com/repos/1/feed\">User</a>",
40
+ "has improved from a B to an A",
41
+ "(<a href=\"https://codeclimate.com/repos/1/compare\">Compare</a>)"
42
+ ].join(" "))
43
+ end
44
+
45
+ def test_quality_declined_without_compare_url
46
+ e = event(:quality, to: "D", from: "C")
47
+
48
+ assert_hipchat_receives("red", e, [
49
+ "[Example]",
50
+ "<a href=\"https://codeclimate.com/repos/1/feed\">User</a>",
51
+ "has declined from a C to a D",
52
+ "(<a href=\"https://codeclimate.com/repos/1/compare\">Compare</a>)"
53
+ ].join(" "))
54
+ end
55
+
56
+ def test_single_vulnerability
57
+ e = event(:vulnerability, vulnerabilities: [
58
+ { "warning_type" => "critical" }
59
+ ])
60
+
61
+ assert_hipchat_receives("red", e, [
62
+ "[Example]",
63
+ "New <a href=\"https://codeclimate.com/repos/1/feed\">critical</a>",
64
+ "issue found",
65
+ ].join(" "))
66
+ end
67
+
68
+ def test_single_vulnerability_with_location
69
+ e = event(:vulnerability, vulnerabilities: [{
70
+ "warning_type" => "critical",
71
+ "location" => "app/user.rb line 120"
72
+ }])
73
+
74
+ assert_hipchat_receives("red", e, [
75
+ "[Example]",
76
+ "New <a href=\"https://codeclimate.com/repos/1/feed\">critical</a>",
77
+ "issue found in app/user.rb line 120",
78
+ ].join(" "))
79
+ end
80
+
81
+ def test_multiple_vulnerabilities
82
+ e = event(:vulnerability, warning_type: "critical", vulnerabilities: [{
83
+ "warning_type" => "unused",
84
+ "location" => "unused"
85
+ }, {
86
+ "warning_type" => "unused",
87
+ "location" => "unused"
88
+ }])
89
+
90
+ assert_hipchat_receives("red", e, [
91
+ "[Example]",
92
+ "2 new <a href=\"https://codeclimate.com/repos/1/feed\">critical</a>",
93
+ "issues found",
94
+ ].join(" "))
95
+ end
96
+
97
+ def test_receive_test
98
+ @stubs.post '/v1/rooms/message' do |env|
99
+ [200, {}, '']
100
+ end
101
+
102
+ response = receive_event(name: "test")
103
+
104
+ assert_equal "Test message sent", response[:message]
105
+ end
106
+
107
+ private
108
+
109
+ def assert_hipchat_receives(color, event_data, expected_body)
110
+ @stubs.post '/v1/rooms/message' do |env|
111
+ body = Hash[URI.decode_www_form(env[:body])]
112
+ assert_equal "token", body["auth_token"]
113
+ assert_equal "123", body["room_id"]
114
+ assert_equal "true", body["notify"]
115
+ assert_equal color, body["color"]
116
+ assert_equal expected_body, body["message"]
117
+ [200, {}, '']
118
+ end
119
+
120
+ receive_event(event_data)
121
+ end
122
+
123
+ def receive_event(event_data = nil)
124
+ receive(
125
+ CC::Service::HipChat,
126
+ { auth_token: "token", room_id: "123", notify: true },
127
+ event_data || event(:quality, from: "C", to: "D")
128
+ )
129
+ end
130
+ end
@@ -0,0 +1,51 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class InvocationErrorHandling < CC::Service::TestCase
4
+ def test_success_returns_upstream_result
5
+ handler = CC::Service::Invocation::WithErrorHandling.new(
6
+ lambda { :success },
7
+ FakeLogger.new,
8
+ "not important"
9
+ )
10
+
11
+ assert_equal :success, handler.call
12
+ end
13
+
14
+ def test_http_errors_return_relevant_data
15
+ logger = FakeLogger.new
16
+ env = {
17
+ status: 401,
18
+ params: "params",
19
+ url: "url"
20
+ }
21
+
22
+ handler = CC::Service::Invocation::WithErrorHandling.new(
23
+ lambda { raise CC::Service::HTTPError.new("foo", env) },
24
+ logger,
25
+ "prefix"
26
+ )
27
+
28
+ result = handler.call
29
+ assert_equal false, result[:ok]
30
+ assert_equal 401, result[:status]
31
+ assert_equal "params", result[:params]
32
+ assert_equal "url", result[:endpoint_url]
33
+ assert_equal "foo", result[:message]
34
+ assert_equal "Exception invoking service: [prefix] (CC::Service::HTTPError) foo. Response: <nil>", result[:log_message]
35
+ end
36
+
37
+ def test_error_returns_a_hash_with_explanations
38
+ logger = FakeLogger.new
39
+
40
+ handler = CC::Service::Invocation::WithErrorHandling.new(
41
+ lambda { raise ArgumentError.new("lol") },
42
+ logger,
43
+ "prefix"
44
+ )
45
+
46
+ result = handler.call
47
+ assert_equal false, result[:ok]
48
+ assert_equal "lol", result[:message]
49
+ assert_equal "Exception invoking service: [prefix] (ArgumentError) lol", result[:log_message]
50
+ end
51
+ end
@@ -0,0 +1,21 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class InvocationReturnValuesTest < CC::Service::TestCase
4
+ def test_success_returns_upstream_result
5
+ handler = CC::Service::Invocation::WithReturnValues.new(
6
+ lambda { :return_value },
7
+ "error message"
8
+ )
9
+
10
+ assert_equal :return_value, handler.call
11
+ end
12
+
13
+ def test_empty_results_returns_hash
14
+ handler = CC::Service::Invocation::WithReturnValues.new(
15
+ lambda { nil },
16
+ "error message"
17
+ )
18
+
19
+ assert_equal( {ok: false, message: "error message"}, handler.call )
20
+ end
21
+ end