codeclimate-services 0.3.0

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 (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