crashbreak 0.0.2 → 0.9.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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/crashbreak.gemspec +3 -0
  3. data/lib/crashbreak.rb +16 -2
  4. data/lib/crashbreak/AWS.rb +30 -0
  5. data/lib/crashbreak/config/configurator.rb +29 -0
  6. data/lib/crashbreak/dumpers_data_repository.rb +34 -0
  7. data/lib/crashbreak/exception_notifier.rb +14 -4
  8. data/lib/crashbreak/exceptions_repository.rb +24 -4
  9. data/lib/crashbreak/github_integration_service.rb +65 -0
  10. data/lib/crashbreak/request_parser.rb +33 -0
  11. data/lib/crashbreak/version.rb +1 -1
  12. data/lib/dumpers/database_dumper.rb +39 -0
  13. data/lib/dumpers/request_dumper.rb +15 -0
  14. data/lib/generators/crashbreak/templates/test.rb +11 -0
  15. data/lib/generators/crashbreak/test_generator.rb +12 -0
  16. data/lib/restorers/database_restorer.rb +35 -0
  17. data/lib/restorers/request_restorer.rb +9 -0
  18. data/lib/restorers/state_restorer.rb +37 -0
  19. data/lib/tasks/crashbreak.rake +14 -0
  20. data/spec/dumpers/request_dumper_spec.rb +14 -0
  21. data/spec/features/sending_error_report_spec.rb +6 -4
  22. data/spec/lib/dumpers_data_repository_spec.rb +19 -0
  23. data/spec/lib/exception_notifier_spec.rb +36 -2
  24. data/spec/lib/exceptions_repository_spec.rb +12 -2
  25. data/spec/lib/generators/test_generator_spec.rb +16 -0
  26. data/spec/lib/github_integration_service_spec.rb +47 -0
  27. data/spec/lib/request_sender_spec.rb +33 -0
  28. data/spec/restorers/state_restorer_spec.rb +29 -0
  29. metadata +67 -8
  30. data/lib/dumpers/program_name_dumper.rb +0 -16
  31. data/lib/restorers/program_name_restorer.rb +0 -7
  32. data/spec/dumpers/program_name_dumper_spec.rb +0 -12
  33. data/spec/restorers/program_name_restorer_spec.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3419000b580aee39142c18531d6386d2fc7835df
4
- data.tar.gz: af8c9aa9154afc6bdeda323201ff02770f9dce6d
3
+ metadata.gz: 29371eea672de29bc9369c0280e99a3c16d08ca8
4
+ data.tar.gz: 432c7a98e58df84245f5b4f27a8aa5cfb541969c
5
5
  SHA512:
6
- metadata.gz: 6977054a05204a526704da6109bce67dddd3b6deba56a26575039b58b7c1ab05abd9156326b0d0e32a156f1612b2b3b9d4b083973b6be737f408206952f11467
7
- data.tar.gz: 5256c559a99c8e87c14b80110579de662f28325bb8865c512fa7195e24c5ca33d0bbe49385be13344e1ed349d4170c51dfe97f2de2484177da94f70b890f59a2
6
+ metadata.gz: 115c2e65b82096eb44a8550a4dbdb6a48a52f05e0358d4913b57a9f1d4e0a40b5b66079aebb4403eb2d646bc881d28b8081478643327ca87346f163a3ec86784
7
+ data.tar.gz: 8046ae60fb3f1722f9c326e932a36901c86612e9a1161b4a06abf7fbb1be8910558705985cf8bdd4797981d808b3095a4312cbdee81a4eacc8ae75658760a218
@@ -22,7 +22,10 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency 'rails'
23
23
  spec.add_development_dependency 'webmock'
24
24
  spec.add_development_dependency 'simplecov'
25
+ spec.add_development_dependency 'deepstruct'
25
26
 
26
27
  spec.add_runtime_dependency 'faraday', '>= 0'
27
28
  spec.add_runtime_dependency 'request_store', '>= 0'
29
+ spec.add_runtime_dependency 'octokit', '>= 0'
30
+ spec.add_runtime_dependency 'aws-sdk', '~> 2'
28
31
  end
@@ -1,6 +1,8 @@
1
1
  require 'rails'
2
2
  require 'faraday'
3
3
  require 'request_store'
4
+ require 'octokit'
5
+ require 'aws-sdk'
4
6
  require 'crashbreak/version'
5
7
  require 'crashbreak/exception_notifier'
6
8
  require 'crashbreak/formatters/basic_formatter'
@@ -13,9 +15,17 @@ require 'crashbreak/config/configurator'
13
15
  require 'crashbreak/config/configurable'
14
16
  require 'crashbreak/exception_catcher_middleware'
15
17
  require 'crashbreak/exceptions_repository'
18
+ require 'crashbreak/dumpers_data_repository'
19
+ require 'crashbreak/request_parser'
20
+ require 'crashbreak/github_integration_service'
21
+ require 'crashbreak/aws'
16
22
 
17
- require 'dumpers/program_name_dumper'
18
- require 'restorers/program_name_restorer'
23
+ require 'dumpers/database_dumper'
24
+ require 'dumpers/request_dumper'
25
+
26
+ require 'restorers/database_restorer'
27
+ require 'restorers/state_restorer'
28
+ require 'restorers/request_restorer'
19
29
 
20
30
  module Crashbreak
21
31
  extend Configurable
@@ -29,4 +39,8 @@ module Crashbreak
29
39
  load 'tasks/crashbreak.rake'
30
40
  end
31
41
  end
42
+
43
+ def self.root
44
+ File.expand_path '../..', __FILE__
45
+ end
32
46
  end
@@ -0,0 +1,30 @@
1
+ module Crashbreak
2
+ module AWS
3
+ def client
4
+ @client ||= Aws::S3::Client.new
5
+ end
6
+
7
+ def bucket_name
8
+ Crashbreak.configure.dumper_options[:aws_bucket_name]
9
+ end
10
+
11
+ def aws_region
12
+ Crashbreak.configure.dumper_options[:aws_region] || ENV['AWS_REGION']
13
+ end
14
+
15
+ def aws_key_id
16
+ Crashbreak.configure.dumper_options[:aws_access_key_id] || ENV['AWS_ACCESS_KEY_ID']
17
+ end
18
+
19
+ def aws_secret_key
20
+ Crashbreak.configure.dumper_options[:aws_secret_access_key] || ENV['AWS_SECRET_ACCESS_KEY']
21
+ end
22
+
23
+ def prepare_aws
24
+ Aws.config.update(
25
+ credentials: Aws::Credentials.new(aws_key_id, aws_secret_key),
26
+ s3: { region: aws_region }
27
+ )
28
+ end
29
+ end
30
+ end
@@ -3,6 +3,15 @@ module Crashbreak
3
3
  attr_accessor :api_key
4
4
  attr_accessor :exception_notifier
5
5
  attr_accessor :error_serializers
6
+ attr_accessor :dumpers
7
+ attr_accessor :dumper_options
8
+ attr_accessor :restorer_options
9
+
10
+ attr_accessor :github_login
11
+ attr_accessor :github_password
12
+ attr_accessor :github_repo_name
13
+ attr_accessor :github_spec_file_path
14
+ attr_accessor :github_development_branch
6
15
 
7
16
  def exception_notifier
8
17
  @exception_notifier || ExceptionNotifier.new
@@ -11,5 +20,25 @@ module Crashbreak
11
20
  def error_serializers
12
21
  @error_serializers || []
13
22
  end
23
+
24
+ def github_spec_file_path
25
+ @github_spec_file_path || 'spec/crashbreak_error_spec.rb'
26
+ end
27
+
28
+ def github_development_branch
29
+ @github_development_branch || 'master'
30
+ end
31
+
32
+ def dumpers
33
+ @dumpers || []
34
+ end
35
+
36
+ def dumper_options
37
+ @dumper_options || {}
38
+ end
39
+
40
+ def restorer_options
41
+ @restorer_options || {}
42
+ end
14
43
  end
15
44
  end
@@ -0,0 +1,34 @@
1
+ module Crashbreak
2
+ class DumpersDataRepository
3
+ BASE_URL = 'http://crashbreak.herokuapp.com/api'
4
+
5
+ def initialize(error_id)
6
+ @error_id = error_id
7
+ end
8
+
9
+ def dumpers_data
10
+ JSON.parse request_body
11
+ end
12
+
13
+ private
14
+
15
+ def request_body
16
+ connection.get do |req|
17
+ req.url request_url
18
+ req.headers['Content-Type'] = 'application/json'
19
+ end.body
20
+ end
21
+
22
+ def connection
23
+ Faraday.new(url: request_url)
24
+ end
25
+
26
+ def request_url
27
+ "#{BASE_URL}/projects/#{project_token}/errors/#{@error_id}/dumpers_data"
28
+ end
29
+
30
+ def project_token
31
+ Crashbreak.configure.api_key
32
+ end
33
+ end
34
+ end
@@ -1,19 +1,29 @@
1
1
  module Crashbreak
2
2
  class ExceptionNotifier
3
3
  def notify
4
- exceptions_repository.create serialize_exception
4
+ error_id = exceptions_repository.create serialize_exception
5
+ GithubIntegrationService.new(error_id).push_test if Crashbreak.configure.github_repo_name.present?
5
6
  end
6
7
 
7
8
  private
8
9
 
9
10
  def serialize_exception
10
11
  {}.tap do |exception_hash|
11
- serializers.each do |serializer|
12
- exception_hash.deep_merge!(serializer.serialize)
13
- end
12
+ serializers.each { |serializer| exception_hash.deep_merge!(serializer.serialize) }
13
+ exception_hash[:dumpers_data] = dumpers_data
14
14
  end
15
15
  end
16
16
 
17
+ def dumpers_data
18
+ dumpers.map do |dumper|
19
+ [dumper.class.to_s, dumper.dump]
20
+ end.to_h
21
+ end
22
+
23
+ def dumpers
24
+ Crashbreak.configure.dumpers
25
+ end
26
+
17
27
  def serializers
18
28
  [BasicInformationFormatter.new] + Crashbreak.configure.error_serializers
19
29
  end
@@ -3,23 +3,43 @@ module Crashbreak
3
3
  BASE_URL = 'http://crashbreak.herokuapp.com/api'
4
4
 
5
5
  def create(error_report_hash)
6
+ JSON.parse(post_request(error_report_hash).body)['id']
7
+ end
8
+
9
+ def resolve(error_id)
10
+ resolve_request error_id
11
+ end
12
+
13
+ private
14
+
15
+ def post_request(error_report_hash)
6
16
  connection.post do |req|
7
- req.url request_url
17
+ req.url create_request_url
8
18
  req.body = error_report_hash.to_json
9
19
  req.headers['Content-Type'] = 'application/json'
10
20
  end
11
21
  end
12
22
 
13
- private
23
+ def resolve_request(error_id)
24
+ connection.put do |req|
25
+ req.url resolve_request_url(error_id)
26
+ req.body = { error_report: { status: :resolved } }.to_json
27
+ req.headers['Content-Type'] = 'application/json'
28
+ end
29
+ end
14
30
 
15
31
  def connection
16
- Faraday.new(url: request_url)
32
+ Faraday.new
17
33
  end
18
34
 
19
- def request_url
35
+ def create_request_url
20
36
  "#{BASE_URL}/projects/#{project_token}/errors"
21
37
  end
22
38
 
39
+ def resolve_request_url(error_id)
40
+ "#{BASE_URL}/projects/#{project_token}/errors/#{error_id}"
41
+ end
42
+
23
43
  def project_token
24
44
  Crashbreak.configure.api_key
25
45
  end
@@ -0,0 +1,65 @@
1
+ module Crashbreak
2
+ class GithubIntegrationService
3
+ def initialize(error_id)
4
+ @error_id = error_id
5
+ end
6
+
7
+ def push_test
8
+ create_branch(branch_sha)
9
+ create_test_file
10
+ end
11
+
12
+ def remove_test
13
+ client.delete_contents(repo_name, file_path, "Remove test for error #{@error_id}",
14
+ test_file_sha, branch: "refs/#{error_branch_name}")
15
+ end
16
+
17
+ def create_pull_request
18
+ client.create_pull_request(repo_name, development_branch_name, error_branch_name.gsub('heads/', ''), "Crashbreak - fix for error #{@error_id}", '')
19
+ end
20
+
21
+ private
22
+
23
+ def branch_sha
24
+ @branch_sha ||= client.ref(repo_name, 'heads/master').object.sha
25
+ end
26
+
27
+ def test_file_sha
28
+ @test_file_sha ||= client.contents(repo_name, path: file_path, ref: error_branch_name).sha
29
+ end
30
+
31
+ def create_branch(sha)
32
+ client.create_ref repo_name, error_branch_name, sha
33
+ end
34
+
35
+ def create_test_file
36
+ client.create_contents repo_name, file_path, "Add test file for error #{@error_id}",
37
+ file_content, { branch: "refs/#{error_branch_name}" }
38
+ end
39
+
40
+ def file_content
41
+ test_file_content = File.read("#{Crashbreak.root}/lib/generators/crashbreak/templates/test.rb")
42
+ test_file_content.gsub('<%= error_id %>', @error_id.to_s)
43
+ end
44
+
45
+ def client
46
+ @client ||= Octokit::Client.new(login: Crashbreak.configure.github_login, password: Crashbreak.configure.github_password)
47
+ end
48
+
49
+ def repo_name
50
+ Crashbreak.configure.github_repo_name
51
+ end
52
+
53
+ def error_branch_name
54
+ "heads/crashbreak-error-#{@error_id}"
55
+ end
56
+
57
+ def file_path
58
+ Crashbreak.configure.github_spec_file_path
59
+ end
60
+
61
+ def development_branch_name
62
+ Crashbreak.configure.github_development_branch
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,33 @@
1
+ module Crashbreak
2
+ class RequestParser
3
+ def initialize(request_data)
4
+ @request = request_data
5
+ end
6
+
7
+ def request_data
8
+ yield(request_method_sym, request_path, request_body, request_headers)
9
+ end
10
+
11
+ private
12
+
13
+ def request_method_sym
14
+ @request['REQUEST_METHOD'].underscore.to_sym
15
+ end
16
+
17
+ def request_path
18
+ @request['PATH_INFO']
19
+ end
20
+
21
+ def request_body
22
+ JSON.parse(request_hash_as_string.gsub('=>', ':')).symbolize_keys
23
+ end
24
+
25
+ def request_hash_as_string
26
+ @request['action_dispatch.request.request_parameters'] || '{}'
27
+ end
28
+
29
+ def request_headers
30
+ @request.select{|key| key.start_with?('HTTP_')}.map{|key, value| [key.gsub('HTTP_', ''), value]}.to_h
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module Crashbreak
2
- VERSION = '0.0.2'
2
+ VERSION = '0.9.0'
3
3
  end
@@ -0,0 +1,39 @@
1
+ module Crashbreak
2
+ class DatabaseDumper
3
+ include Crashbreak::AWS
4
+
5
+ def dump
6
+ dump_database
7
+ prepare_aws
8
+ upload_dump
9
+ remove_locally_dump
10
+ { file_name: aws_file_name }
11
+ end
12
+
13
+ private
14
+
15
+ def dump_database
16
+ system(dump_command)
17
+ end
18
+
19
+ def upload_dump
20
+ client.put_object(bucket: bucket_name, key: aws_file_name, body: File.read(dump_location))
21
+ end
22
+
23
+ def remove_locally_dump
24
+ File.delete(dump_location)
25
+ end
26
+
27
+ def aws_file_name
28
+ @aws_file_name ||= "Crashbreak - database dump #{DateTime.now.utc}"
29
+ end
30
+
31
+ def dump_location
32
+ Crashbreak.configure.dumper_options[:dump_location]
33
+ end
34
+
35
+ def dump_command
36
+ Crashbreak.configure.dumper_options[:dump_command]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ class RequestDumper < Crashbreak::BasicFormatter
2
+ def dump
3
+ sanitized_request_hash
4
+ end
5
+
6
+ private
7
+
8
+ def sanitized_request_hash
9
+ request_hash.map{|key, value| [key, value.to_s]}.to_h
10
+ end
11
+
12
+ def request_hash
13
+ request.env
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ describe 'error id: <%= error_id %>', type: :request do
2
+
3
+ let(:restorers_data) { StateRestorer.new('<%= error_id %>').restore }
4
+ let(:request_parser) { Crashbreak::RequestParser.new restorers_data[:request] }
5
+
6
+ it 'sends request' do
7
+ request_parser.request_data do |request_method, url, body, headers|
8
+ method(request_method).call url, body, headers
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module Crashbreak
2
+ module Generators
3
+ class TestGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('../templates', __FILE__)
5
+ argument :error_id, type: :string
6
+
7
+ def create_test_file
8
+ template 'test.rb', 'spec/error_request_spec.rb'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ module Crashbreak
2
+ class DatabaseRestorer
3
+ include AWS
4
+
5
+ def initialize(dump_data)
6
+ @file_name = dump_data['file_name']
7
+ end
8
+
9
+ def restore
10
+ system('dropdb crashbreak-test')
11
+ system('createdb -T template0 crashbreak-test')
12
+ prepare_aws
13
+ download_dump
14
+ restore_database
15
+ end
16
+
17
+ private
18
+
19
+ def restore_database
20
+ system(restore_command)
21
+ end
22
+
23
+ def download_dump
24
+ File.open("#{Rails.root}/tmp/db.dump", 'wb') do |file|
25
+ client.get_object(bucket: bucket_name, key: @file_name) do |data|
26
+ file.write(data)
27
+ end
28
+ end
29
+ end
30
+
31
+ def restore_command
32
+ Crashbreak.configure.restorer_options[:restore_command]
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,9 @@
1
+ class RequestRestorer
2
+ def initialize(request_data)
3
+ @request_data = request_data
4
+ end
5
+
6
+ def restore
7
+ @request_data
8
+ end
9
+ end
@@ -0,0 +1,37 @@
1
+ class StateRestorer
2
+
3
+ def initialize(error_id)
4
+ @error_id = error_id
5
+ end
6
+
7
+ def restore
8
+ {}.tap do |restorers_hash|
9
+ restorers.each do |restorer|
10
+ restorers_hash[key_name(restorer)] = restorer.new(dumper_data(restorer)).restore
11
+ end
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def dumpers_data
18
+ @dumpers_data ||= Crashbreak::DumpersDataRepository.new(@error_id).dumpers_data
19
+ end
20
+
21
+ def restorers
22
+ dumpers_data.keys.map {|dumper_name| restorer_class_by_dumper_name(dumper_name)}
23
+ end
24
+
25
+ def restorer_class_by_dumper_name(dumper_name)
26
+ dumper_name.gsub('Dumper', 'Restorer').constantize
27
+ end
28
+
29
+ def dumper_data(restorer)
30
+ dumper_name = restorer.to_s.gsub('Restorer', 'Dumper')
31
+ dumpers_data[dumper_name]
32
+ end
33
+
34
+ def key_name(restorer)
35
+ restorer.to_s.gsub('Restorer', '').underscore.to_sym
36
+ end
37
+ end
@@ -10,6 +10,20 @@ namespace :crashbreak do
10
10
 
11
11
  puts 'Done, now check if error exists on crashbreak.com!'
12
12
  end
13
+
14
+ task resolve_error: :environment do
15
+ return puts 'error_id must be set (e.g rake crashbreak:resolve_error error_id=123)' if ENV['error_id'].nil?
16
+ service = Crashbreak::GithubIntegrationService.new ENV['error_id']
17
+
18
+ puts 'Removing test file from github...'
19
+ service.remove_test
20
+
21
+ puts 'Creating pull request'
22
+ service.create_pull_request
23
+
24
+ puts 'Resolving error in CrashBreak...'
25
+ Crashbreak::ExceptionsRepository.new.resolve ENV['error_id']
26
+ end
13
27
  end
14
28
 
15
29
  class CrashbreakTestError < StandardError
@@ -0,0 +1,14 @@
1
+ describe RequestDumper do
2
+ subject { described_class.new }
3
+
4
+ let(:example_request) { double(:request, env: Hash['REQUEST_URI' => 'example_uri']) }
5
+
6
+ before(:each) do
7
+ RequestStore.store[:request] = example_request
8
+ end
9
+
10
+ it 'returns a request hash' do
11
+ expect(subject.dump).to eq example_request.env
12
+ end
13
+
14
+ end
@@ -12,15 +12,16 @@ describe 'Sending error report to server' do
12
12
  before(:each) do
13
13
  Crashbreak.configure.api_key = project_token
14
14
  Crashbreak.configure.error_serializers = [summary_formatter, Crashbreak::EnvironmentVariablesFormatter.new, TestErrorFormatter.new]
15
+ Crashbreak.configure.dumpers = [RequestDumper.new]
15
16
 
16
17
  allow(crashing_app).to receive(:call).and_raise(example_error)
17
18
  allow(example_error).to receive(:backtrace).and_return(%w(example backtrace))
18
- allow(summary_formatter).to receive(:request).and_return(example_request)
19
+ allow_any_instance_of(Crashbreak::BasicFormatter).to receive(:request).and_return(example_request)
19
20
  end
20
21
 
21
22
  let!(:create_exception_request) do
22
23
  stub_request(:post, "#{Crashbreak::ExceptionsRepository::BASE_URL}/projects/#{project_token}/errors").
23
- with(body: error_report_hash.to_json).to_return(status: 200)
24
+ with(body: error_report_hash.to_json).to_return(status: 200, body: { id: 1 }.to_json)
24
25
  end
25
26
 
26
27
  let(:error_report_hash) do
@@ -28,7 +29,8 @@ describe 'Sending error report to server' do
28
29
  name: example_error.to_s, message: example_error.message, backtrace: example_error.backtrace, environment: 'test',
29
30
  summary: { action: example_request.env['PATH_INFO'], controller_name: example_controller.class.to_s,
30
31
  file: example_error.backtrace[0], url: example_request.env['REQUEST_URI'], user_agent: example_request.env['HTTP_USER_AGENT'] },
31
- additional_data: { environment: ENV.to_hash, test: { formatter: true } }
32
+ additional_data: { environment: ENV.to_hash, test: { formatter: true } },
33
+ dumpers_data: { 'RequestDumper' => example_request.env }
32
34
  }
33
35
  end
34
36
 
@@ -36,4 +38,4 @@ describe 'Sending error report to server' do
36
38
  expect{ catching_middleware.call(env) }.to raise_error(TestError)
37
39
  expect(create_exception_request).to have_been_made
38
40
  end
39
- end
41
+ end
@@ -0,0 +1,19 @@
1
+ describe Crashbreak::DumpersDataRepository do
2
+ subject { described_class.new error_id }
3
+
4
+ let(:error_id) { 1 }
5
+ let(:project_token) { 'example_project_token' }
6
+
7
+ before(:each) do
8
+ Crashbreak.configure.api_key = project_token
9
+
10
+ stub_request(:get, "#{described_class::BASE_URL}/projects/#{project_token}/errors/#{error_id}/dumpers_data").
11
+ to_return(status: 200, body: dumpers_data.to_json)
12
+ end
13
+
14
+ let(:dumpers_data) { Hash['example_dumpers_data' => 'example'] }
15
+
16
+ it 'sends request to create exception report' do
17
+ expect(subject.dumpers_data).to eq dumpers_data
18
+ end
19
+ end
@@ -6,19 +6,53 @@ describe Crashbreak::ExceptionNotifier do
6
6
 
7
7
  before(:each) do
8
8
  allow(error).to receive(:backtrace).and_return(%w(example backtrace))
9
+ allow_any_instance_of(Crashbreak::Configurator).to receive(:error_serializers).and_return([])
10
+ allow_any_instance_of(described_class).to receive(:dumpers).and_return([])
11
+ allow_any_instance_of(Crashbreak::GithubIntegrationService).to receive(:push_test).and_return(true)
12
+
9
13
  RequestStore[:exception] = error
14
+ RequestStore[:request] = double(:request, env: { request: :example_request_data } )
10
15
  end
11
16
 
12
17
  let(:exception_basic_hash) do
13
- { name: error_name, message: error_message, backtrace: error.backtrace, environment: ENV['RACK_ENV'] }
18
+ { name: error_name, message: error_message, backtrace: error.backtrace, environment: ENV['RACK_ENV'], dumpers_data: {} }
14
19
  end
15
20
 
16
21
  context 'without additional serializers' do
17
- before(:each) { allow_any_instance_of(Crashbreak::Configurator).to receive(:error_serializers).and_return([]) }
18
22
  it 'sends pure error' do
19
23
  expect_any_instance_of(Crashbreak::ExceptionsRepository).to receive(:create).with(exception_basic_hash)
20
24
  subject.notify
21
25
  end
26
+
27
+ context 'with dumpers' do
28
+ let(:expected_hash) { exception_basic_hash.merge(dumpers_hash) }
29
+
30
+ let(:dumpers_hash) do
31
+ { dumpers_data: { 'RequestDumper' => { request: 'example_request_data' } } }
32
+ end
33
+
34
+ before(:each) do
35
+ allow_any_instance_of(described_class).to receive(:dumpers).and_return([RequestDumper.new])
36
+ end
37
+
38
+ it 'sends dump data' do
39
+ expect_any_instance_of(Crashbreak::ExceptionsRepository).to receive(:create).with(expected_hash)
40
+ subject.notify
41
+ end
42
+ end
43
+
44
+ context 'github integration' do
45
+ let(:error_id) { 1 }
46
+
47
+ it 'passes error id from request to github integration service' do
48
+ Crashbreak.configure.github_repo_name = 'user/repo'
49
+
50
+ allow_any_instance_of(Crashbreak::ExceptionsRepository).to receive(:create).and_return(error_id)
51
+ expect_any_instance_of(Crashbreak::GithubIntegrationService).to receive(:initialize).with(error_id)
52
+
53
+ subject.notify
54
+ end
55
+ end
22
56
  end
23
57
 
24
58
  context 'with additional serializers' do
@@ -2,6 +2,7 @@ describe Crashbreak::ExceptionsRepository do
2
2
  subject { described_class.new }
3
3
 
4
4
  let(:project_token) { 'example_project_token' }
5
+ let(:exception_id) { 1 }
5
6
 
6
7
  before(:each) do
7
8
  Crashbreak.configure.api_key = project_token
@@ -16,11 +17,20 @@ describe Crashbreak::ExceptionsRepository do
16
17
 
17
18
  let!(:create_exception_request) do
18
19
  stub_request(:post, "#{described_class::BASE_URL}/projects/#{project_token}/errors").
19
- with(body: error_report_hash.to_json).to_return(status: 200)
20
+ with(body: error_report_hash.to_json).to_return(status: 200, body: { id: exception_id }.to_json)
21
+ end
22
+
23
+ let!(:resolve_exception_request) do
24
+ stub_request(:put, "#{described_class::BASE_URL}/projects/#{project_token}/errors/#{exception_id}").
25
+ with(body: { error_report: { status: :resolved } }.to_json)
20
26
  end
21
27
 
22
28
  it 'sends request to create exception report' do
23
- subject.create error_report_hash
29
+ expect(subject.create error_report_hash).to eq exception_id
24
30
  expect(create_exception_request).to have_been_made
25
31
  end
32
+
33
+ it 'resolves error' do
34
+ subject.resolve exception_id
35
+ end
26
36
  end
@@ -0,0 +1,16 @@
1
+ require 'generator_spec'
2
+ require_relative '../../../lib/generators/crashbreak/test_generator'
3
+
4
+ describe Crashbreak::Generators::TestGenerator do
5
+ destination File.expand_path('../../../../tmp', __FILE__)
6
+ arguments %w(example_error_id)
7
+
8
+ before(:all) do
9
+ prepare_destination
10
+ run_generator
11
+ end
12
+
13
+ it 'creates a test' do
14
+ assert_file 'spec/error_request_spec.rb', /example_error_id/
15
+ end
16
+ end
@@ -0,0 +1,47 @@
1
+ describe Crashbreak::GithubIntegrationService do
2
+ subject { described_class.new error_id }
3
+
4
+ let(:error_id) { 1 }
5
+ let(:branch_name) { 'refs/heads/crashbreak-error-1' }
6
+ let(:git_sha) { 'example_sha' }
7
+ let(:file_content) { 'example_file_content' }
8
+
9
+ let(:github_refs_url) { 'https://api.github.com/repos/user/repo/git/refs' }
10
+ let(:github_master_ref_url) { "#{github_refs_url}/heads/master" }
11
+ let(:github_create_content_url) { 'https://api.github.com/repos/user/repo/contents' }
12
+ let(:github_test_file_url) { 'https://api.github.com/repos/user/repo/contents/spec/crashbreak_error_spec.rb' }
13
+ let(:github_pull_requests_url) { 'https://api.github.com/repos/user/repo/pulls' }
14
+
15
+ before(:each) do
16
+ Crashbreak.configure.github_spec_file_path = 'spec/crashbreak_error_spec.rb'
17
+ end
18
+
19
+ it 'pushes test to github' do
20
+ stub_request(:get, github_master_ref_url).to_return(body: DeepStruct.wrap(object: { sha: git_sha } ))
21
+
22
+ stub_request(:post, github_refs_url).with(body: { ref: 'refs/heads/crashbreak-error-1', sha: git_sha}.to_json)
23
+
24
+ stub_request(:put, "#{github_create_content_url}/#{Crashbreak.configure.github_spec_file_path}").
25
+ with(body: { branch: branch_name, content: Base64.strict_encode64(file_content), message: 'Add test file for error 1' }.to_json)
26
+
27
+ allow(subject).to receive(:file_content).and_return(file_content)
28
+
29
+ subject.push_test
30
+ end
31
+
32
+ it 'removes test from github' do
33
+ stub_request(:get, "#{github_test_file_url}?ref=heads/crashbreak-error-1").to_return(body: DeepStruct.wrap(sha: git_sha))
34
+
35
+ stub_request(:delete, github_test_file_url).
36
+ with(body: { branch: 'refs/heads/crashbreak-error-1', message: 'Remove test for error 1', sha: git_sha }.to_json)
37
+
38
+ subject.remove_test
39
+ end
40
+
41
+ it 'creates pull request' do
42
+ stub_request(:post, github_pull_requests_url).
43
+ with(body: { base: 'master', head: 'crashbreak-error-1', title: 'Crashbreak - fix for error 1', body: '' }.to_json)
44
+
45
+ subject.create_pull_request
46
+ end
47
+ end
@@ -0,0 +1,33 @@
1
+ describe Crashbreak::RequestParser do
2
+ subject { described_class.new request_data }
3
+
4
+ context 'simple get without headers' do
5
+ let(:request_data) { Hash['REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/projects' ]}
6
+
7
+ it 'yields with request data' do
8
+ expect{|y| subject.request_data(&y) }.to yield_with_args(:get, '/projects', {}, {})
9
+ end
10
+ end
11
+
12
+ context 'get with headers' do
13
+ let(:request_data) { Hash['REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/projects',
14
+ 'HTTP_ACCEPT' => 'text/html', 'HTTP_CACHE_CONTROL' => 'max-age=0']}
15
+
16
+ it 'yields with request data' do
17
+ expect{|y| subject.request_data(&y) }.to yield_with_args(:get, '/projects', {}, { 'ACCEPT' => 'text/html', 'CACHE_CONTROL' => 'max-age=0' })
18
+ end
19
+ end
20
+
21
+ context 'post with body' do
22
+ let(:request_data) { Hash['REQUEST_METHOD' => 'POST', 'PATH_INFO' => '/projects',
23
+ 'HTTP_ACCEPT' => 'text/html', 'HTTP_CACHE_CONTROL' => 'max-age=0',
24
+ 'action_dispatch.request.request_parameters' => request_parameters.to_json
25
+ ]}
26
+
27
+ let(:request_parameters) { Hash[test_parameter: 'test_value', second_test_parameter: 'second_test_value'] }
28
+
29
+ it 'yields with request data' do
30
+ expect{|y| subject.request_data(&y) }.to yield_with_args(:post, '/projects', request_parameters, { 'ACCEPT' => 'text/html', 'CACHE_CONTROL' => 'max-age=0' })
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ describe StateRestorer do
2
+ subject { described_class.new error_id }
3
+ let(:error_id) { 1 }
4
+ let(:project_token) { 'example_project_token'}
5
+
6
+ before(:each) do
7
+ Crashbreak.configure.api_key = project_token
8
+
9
+ stub_request(:get, "#{Crashbreak::DumpersDataRepository::BASE_URL}/projects/#{project_token}/errors/#{error_id}/dumpers_data").
10
+ to_return(status: 200, body: dumpers_data.to_json)
11
+ end
12
+
13
+ let(:dumpers_data) { Hash['TestDumper' => 'test_data'] }
14
+
15
+ it 'returns a hash with all restorers data' do
16
+ expect_any_instance_of(TestRestorer).to receive(:initialize).with('test_data')
17
+ expect(subject.restore).to eq(test: 'restored_test_data')
18
+ end
19
+ end
20
+
21
+ class TestRestorer
22
+ def initialize(data)
23
+ @data = data
24
+ end
25
+
26
+ def restore
27
+ 'restored_test_data'
28
+ end
29
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crashbreak
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Janeczek
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-20 00:00:00.000000000 Z
11
+ date: 2015-04-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: deepstruct
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: faraday
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +136,34 @@ dependencies:
122
136
  - - ">="
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: octokit
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: aws-sdk
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2'
125
167
  description: Maybe later... :)
126
168
  email:
127
169
  - michal.janeczek@ymail.com
@@ -138,8 +180,10 @@ files:
138
180
  - Rakefile
139
181
  - crashbreak.gemspec
140
182
  - lib/crashbreak.rb
183
+ - lib/crashbreak/AWS.rb
141
184
  - lib/crashbreak/config/configurable.rb
142
185
  - lib/crashbreak/config/configurator.rb
186
+ - lib/crashbreak/dumpers_data_repository.rb
143
187
  - lib/crashbreak/exception_catcher_middleware.rb
144
188
  - lib/crashbreak/exception_notifier.rb
145
189
  - lib/crashbreak/exceptions_repository.rb
@@ -149,16 +193,24 @@ files:
149
193
  - lib/crashbreak/formatters/environment_variables_formatter.rb
150
194
  - lib/crashbreak/formatters/hash_formatter.rb
151
195
  - lib/crashbreak/formatters/summary_formatter.rb
196
+ - lib/crashbreak/github_integration_service.rb
197
+ - lib/crashbreak/request_parser.rb
152
198
  - lib/crashbreak/version.rb
153
- - lib/dumpers/program_name_dumper.rb
199
+ - lib/dumpers/database_dumper.rb
200
+ - lib/dumpers/request_dumper.rb
154
201
  - lib/generators/crashbreak/install_generator.rb
155
202
  - lib/generators/crashbreak/templates/crashbreak.rb
156
- - lib/restorers/program_name_restorer.rb
203
+ - lib/generators/crashbreak/templates/test.rb
204
+ - lib/generators/crashbreak/test_generator.rb
205
+ - lib/restorers/database_restorer.rb
206
+ - lib/restorers/request_restorer.rb
207
+ - lib/restorers/state_restorer.rb
157
208
  - lib/tasks/crashbreak.rake
158
- - spec/dumpers/program_name_dumper_spec.rb
209
+ - spec/dumpers/request_dumper_spec.rb
159
210
  - spec/features/sending_error_report_spec.rb
160
211
  - spec/lib/config/configurable_spec.rb
161
212
  - spec/lib/config/configurator_spec.rb
213
+ - spec/lib/dumpers_data_repository_spec.rb
162
214
  - spec/lib/exception_catcher_middleware_spec.rb
163
215
  - spec/lib/exception_notifier_spec.rb
164
216
  - spec/lib/exceptions_repository_spec.rb
@@ -167,7 +219,10 @@ files:
167
219
  - spec/lib/formatters/group_formatter_spec.rb
168
220
  - spec/lib/formatters/summary_formatter_spec.rb
169
221
  - spec/lib/generators/install_generator_spec.rb
170
- - spec/restorers/program_name_restorer_spec.rb
222
+ - spec/lib/generators/test_generator_spec.rb
223
+ - spec/lib/github_integration_service_spec.rb
224
+ - spec/lib/request_sender_spec.rb
225
+ - spec/restorers/state_restorer_spec.rb
171
226
  - spec/spec_helper.rb
172
227
  - spec/support/rspec_config.rb
173
228
  - spec/support/test_error.rb
@@ -197,10 +252,11 @@ signing_key:
197
252
  specification_version: 4
198
253
  summary: Take a break from crashes!
199
254
  test_files:
200
- - spec/dumpers/program_name_dumper_spec.rb
255
+ - spec/dumpers/request_dumper_spec.rb
201
256
  - spec/features/sending_error_report_spec.rb
202
257
  - spec/lib/config/configurable_spec.rb
203
258
  - spec/lib/config/configurator_spec.rb
259
+ - spec/lib/dumpers_data_repository_spec.rb
204
260
  - spec/lib/exception_catcher_middleware_spec.rb
205
261
  - spec/lib/exception_notifier_spec.rb
206
262
  - spec/lib/exceptions_repository_spec.rb
@@ -209,7 +265,10 @@ test_files:
209
265
  - spec/lib/formatters/group_formatter_spec.rb
210
266
  - spec/lib/formatters/summary_formatter_spec.rb
211
267
  - spec/lib/generators/install_generator_spec.rb
212
- - spec/restorers/program_name_restorer_spec.rb
268
+ - spec/lib/generators/test_generator_spec.rb
269
+ - spec/lib/github_integration_service_spec.rb
270
+ - spec/lib/request_sender_spec.rb
271
+ - spec/restorers/state_restorer_spec.rb
213
272
  - spec/spec_helper.rb
214
273
  - spec/support/rspec_config.rb
215
274
  - spec/support/test_error.rb
@@ -1,16 +0,0 @@
1
- module Crashbreak
2
- class ProgramNameDumper
3
- def dump
4
- dump_file.tap do |file|
5
- file.puts $PROGRAM_NAME
6
- file.close
7
- end
8
- end
9
-
10
- private
11
-
12
- def dump_file
13
- File.new('program_name.dump', 'w')
14
- end
15
- end
16
- end
@@ -1,7 +0,0 @@
1
- module Crashbreak
2
- class ProgramNameRestorer
3
- def restore
4
- $PROGRAM_NAME = File.readlines('program_name.dump')[0]
5
- end
6
- end
7
- end
@@ -1,12 +0,0 @@
1
- describe Crashbreak::ProgramNameDumper do
2
- subject { described_class.new }
3
- let(:file_path) { subject.dump.path }
4
-
5
- it 'file name should be relevant to class' do
6
- expect(File.basename(file_path)).to eq 'program_name.dump'
7
- end
8
-
9
- it 'checks file content' do
10
- expect(File.read(file_path)).to match $PROGRAM_NAME
11
- end
12
- end
@@ -1,11 +0,0 @@
1
- describe Crashbreak::ProgramNameRestorer do
2
- subject { described_class.new }
3
- let(:program_name) { 'crashbreak' }
4
-
5
- before(:each) { allow(File).to receive(:readlines).and_return([program_name]) }
6
-
7
- it 'restore global variable $PROGRAM_NAME from file' do
8
- subject.restore
9
- expect($PROGRAM_NAME).to eq program_name
10
- end
11
- end