crashbreak 0.0.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/crashbreak.gemspec +3 -0
- data/lib/crashbreak.rb +16 -2
- data/lib/crashbreak/AWS.rb +30 -0
- data/lib/crashbreak/config/configurator.rb +29 -0
- data/lib/crashbreak/dumpers_data_repository.rb +34 -0
- data/lib/crashbreak/exception_notifier.rb +14 -4
- data/lib/crashbreak/exceptions_repository.rb +24 -4
- data/lib/crashbreak/github_integration_service.rb +65 -0
- data/lib/crashbreak/request_parser.rb +33 -0
- data/lib/crashbreak/version.rb +1 -1
- data/lib/dumpers/database_dumper.rb +39 -0
- data/lib/dumpers/request_dumper.rb +15 -0
- data/lib/generators/crashbreak/templates/test.rb +11 -0
- data/lib/generators/crashbreak/test_generator.rb +12 -0
- data/lib/restorers/database_restorer.rb +35 -0
- data/lib/restorers/request_restorer.rb +9 -0
- data/lib/restorers/state_restorer.rb +37 -0
- data/lib/tasks/crashbreak.rake +14 -0
- data/spec/dumpers/request_dumper_spec.rb +14 -0
- data/spec/features/sending_error_report_spec.rb +6 -4
- data/spec/lib/dumpers_data_repository_spec.rb +19 -0
- data/spec/lib/exception_notifier_spec.rb +36 -2
- data/spec/lib/exceptions_repository_spec.rb +12 -2
- data/spec/lib/generators/test_generator_spec.rb +16 -0
- data/spec/lib/github_integration_service_spec.rb +47 -0
- data/spec/lib/request_sender_spec.rb +33 -0
- data/spec/restorers/state_restorer_spec.rb +29 -0
- metadata +67 -8
- data/lib/dumpers/program_name_dumper.rb +0 -16
- data/lib/restorers/program_name_restorer.rb +0 -7
- data/spec/dumpers/program_name_dumper_spec.rb +0 -12
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29371eea672de29bc9369c0280e99a3c16d08ca8
|
4
|
+
data.tar.gz: 432c7a98e58df84245f5b4f27a8aa5cfb541969c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 115c2e65b82096eb44a8550a4dbdb6a48a52f05e0358d4913b57a9f1d4e0a40b5b66079aebb4403eb2d646bc881d28b8081478643327ca87346f163a3ec86784
|
7
|
+
data.tar.gz: 8046ae60fb3f1722f9c326e932a36901c86612e9a1161b4a06abf7fbb1be8910558705985cf8bdd4797981d808b3095a4312cbdee81a4eacc8ae75658760a218
|
data/crashbreak.gemspec
CHANGED
@@ -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
|
data/lib/crashbreak.rb
CHANGED
@@ -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/
|
18
|
-
require '
|
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
|
12
|
-
|
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
|
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
|
-
|
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
|
32
|
+
Faraday.new
|
17
33
|
end
|
18
34
|
|
19
|
-
def
|
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
|
data/lib/crashbreak/version.rb
CHANGED
@@ -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,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,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
|
data/lib/tasks/crashbreak.rake
CHANGED
@@ -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
|
-
|
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
|
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-
|
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/
|
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/
|
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/
|
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/
|
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/
|
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/
|
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,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
|