crashbreak 0.0.2 → 0.9.0

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