ably 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +9 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +8 -1
  6. data/Rakefile +10 -0
  7. data/ably.gemspec +18 -18
  8. data/lib/ably.rb +6 -5
  9. data/lib/ably/auth.rb +11 -14
  10. data/lib/ably/exceptions.rb +18 -15
  11. data/lib/ably/logger.rb +102 -0
  12. data/lib/ably/models/error_info.rb +1 -1
  13. data/lib/ably/models/message.rb +19 -5
  14. data/lib/ably/models/message_encoders/base.rb +107 -0
  15. data/lib/ably/models/message_encoders/base64.rb +39 -0
  16. data/lib/ably/models/message_encoders/cipher.rb +80 -0
  17. data/lib/ably/models/message_encoders/json.rb +33 -0
  18. data/lib/ably/models/message_encoders/utf8.rb +33 -0
  19. data/lib/ably/models/paginated_resource.rb +23 -6
  20. data/lib/ably/models/presence_message.rb +19 -7
  21. data/lib/ably/models/protocol_message.rb +5 -4
  22. data/lib/ably/models/token.rb +2 -2
  23. data/lib/ably/modules/channels_collection.rb +0 -3
  24. data/lib/ably/modules/conversions.rb +3 -3
  25. data/lib/ably/modules/encodeable.rb +68 -0
  26. data/lib/ably/modules/event_emitter.rb +10 -4
  27. data/lib/ably/modules/event_machine_helpers.rb +6 -4
  28. data/lib/ably/modules/http_helpers.rb +7 -2
  29. data/lib/ably/modules/model_common.rb +2 -0
  30. data/lib/ably/modules/state_emitter.rb +10 -1
  31. data/lib/ably/realtime.rb +19 -12
  32. data/lib/ably/realtime/channel.rb +26 -13
  33. data/lib/ably/realtime/client.rb +31 -7
  34. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -3
  35. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +13 -4
  36. data/lib/ably/realtime/connection.rb +152 -46
  37. data/lib/ably/realtime/connection/connection_manager.rb +168 -0
  38. data/lib/ably/realtime/connection/connection_state_machine.rb +56 -33
  39. data/lib/ably/realtime/connection/websocket_transport.rb +56 -29
  40. data/lib/ably/{models → realtime/models}/nil_channel.rb +1 -1
  41. data/lib/ably/realtime/presence.rb +38 -13
  42. data/lib/ably/rest.rb +7 -5
  43. data/lib/ably/rest/channel.rb +24 -3
  44. data/lib/ably/rest/client.rb +56 -17
  45. data/lib/ably/rest/middleware/encoder.rb +49 -0
  46. data/lib/ably/rest/middleware/exceptions.rb +3 -2
  47. data/lib/ably/rest/middleware/logger.rb +37 -0
  48. data/lib/ably/rest/presence.rb +10 -2
  49. data/lib/ably/util/crypto.rb +57 -29
  50. data/lib/ably/util/pub_sub.rb +11 -0
  51. data/lib/ably/version.rb +1 -1
  52. data/spec/acceptance/realtime/channel_spec.rb +65 -7
  53. data/spec/acceptance/realtime/connection_spec.rb +123 -27
  54. data/spec/acceptance/realtime/message_spec.rb +319 -34
  55. data/spec/acceptance/realtime/presence_history_spec.rb +58 -0
  56. data/spec/acceptance/realtime/presence_spec.rb +160 -18
  57. data/spec/acceptance/rest/auth_spec.rb +93 -49
  58. data/spec/acceptance/rest/base_spec.rb +10 -10
  59. data/spec/acceptance/rest/channel_spec.rb +35 -19
  60. data/spec/acceptance/rest/channels_spec.rb +8 -8
  61. data/spec/acceptance/rest/message_spec.rb +224 -0
  62. data/spec/acceptance/rest/presence_spec.rb +159 -23
  63. data/spec/acceptance/rest/stats_spec.rb +5 -5
  64. data/spec/acceptance/rest/time_spec.rb +4 -4
  65. data/spec/integration/rest/auth.rb +1 -1
  66. data/spec/resources/crypto-data-128.json +56 -0
  67. data/spec/resources/crypto-data-256.json +56 -0
  68. data/spec/rspec_config.rb +39 -0
  69. data/spec/spec_helper.rb +4 -42
  70. data/spec/support/api_helper.rb +1 -1
  71. data/spec/support/event_machine_helper.rb +0 -5
  72. data/spec/support/protocol_msgbus_helper.rb +3 -3
  73. data/spec/support/test_app.rb +3 -3
  74. data/spec/unit/logger_spec.rb +135 -0
  75. data/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  76. data/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  77. data/spec/unit/models/message_encoders/json_spec.rb +135 -0
  78. data/spec/unit/models/message_encoders/utf8_spec.rb +100 -0
  79. data/spec/unit/models/message_spec.rb +16 -1
  80. data/spec/unit/models/paginated_resource_spec.rb +46 -0
  81. data/spec/unit/models/presence_message_spec.rb +18 -5
  82. data/spec/unit/models/token_spec.rb +1 -1
  83. data/spec/unit/modules/event_emitter_spec.rb +24 -10
  84. data/spec/unit/realtime/channel_spec.rb +3 -3
  85. data/spec/unit/realtime/channels_spec.rb +1 -1
  86. data/spec/unit/realtime/client_spec.rb +44 -2
  87. data/spec/unit/realtime/connection_spec.rb +2 -2
  88. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +4 -4
  89. data/spec/unit/realtime/presence_spec.rb +1 -1
  90. data/spec/unit/realtime/realtime_spec.rb +3 -3
  91. data/spec/unit/realtime/websocket_transport_spec.rb +24 -0
  92. data/spec/unit/rest/channels_spec.rb +1 -1
  93. data/spec/unit/rest/client_spec.rb +45 -10
  94. data/spec/unit/util/crypto_spec.rb +82 -0
  95. data/spec/unit/{modules → util}/pub_sub_spec.rb +13 -1
  96. metadata +43 -12
  97. data/spec/acceptance/crypto.rb +0 -63
@@ -1,10 +1,10 @@
1
- require "spec_helper"
2
- require "securerandom"
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
3
 
4
- describe "REST" do
4
+ describe 'Ably::Rest Stats' do
5
5
  [:json, :msgpack].each do |protocol|
6
6
  context "over #{protocol}" do
7
- describe "fetching application stats" do
7
+ describe 'fetching application stats' do
8
8
  before(:context) do
9
9
  reload_test_app
10
10
  end
@@ -40,7 +40,7 @@ describe "REST" do
40
40
 
41
41
  [:minute, :hour, :day, :month].each do |interval|
42
42
  context "by #{interval}" do
43
- it "should return all the stats for the application" do
43
+ it 'should return all the stats for the application' do
44
44
  stats = @context_client.stats(start: @interval_start * 1000, by: interval.to_s, direction: 'forwards')
45
45
 
46
46
  expect(stats.size).to eql(1)
@@ -1,14 +1,14 @@
1
- require "spec_helper"
2
- require "securerandom"
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
3
 
4
- describe "REST" do
4
+ describe 'Ably::REST time' do
5
5
  [:msgpack, :json].each do |protocol|
6
6
  context "over #{protocol}" do
7
7
  let(:client) do
8
8
  Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
9
9
  end
10
10
 
11
- describe "fetching the service time" do
11
+ describe 'fetching the service time' do
12
12
  it "should return the service time as a Time object" do
13
13
  expect(client.time).to be_within(2).of(Time.now)
14
14
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Ably::Auth do
4
4
  let(:client) { Ably::Rest::Client.new(key_id: 'id', key_secret: 'secret') }
5
5
 
6
- it "has immutable options" do
6
+ it 'has immutable options' do
7
7
  expect { client.auth.options['key_id'] = 'new_id' }.to raise_error RuntimeError, /can't modify frozen Hash/
8
8
  end
9
9
  end
@@ -0,0 +1,56 @@
1
+ {
2
+ "algorithm": "aes",
3
+ "mode": "cbc",
4
+ "keylength": 128,
5
+ "key": "WUP6u0K7MXI5Zeo0VppPwg==",
6
+ "iv": "HO4cYSP8LybPYBPZPHQOtg==",
7
+ "items": [
8
+ {
9
+ "encoded": {
10
+ "name": "example",
11
+ "data": "The quick brown fox jumped over the lazy dog"
12
+ },
13
+ "encrypted": {
14
+ "name": "example",
15
+ "data": "HO4cYSP8LybPYBPZPHQOtmHItcxYdSvcNUC6kXVpMn0VFL+9z2/5tJ6WFbR0SBT1xhFRuJ+MeBGTU3yOY9P5ow==",
16
+ "encoding": "utf-8/cipher+aes-128-cbc/base64"
17
+ }
18
+ },
19
+ {
20
+ "encoded": {
21
+ "name": "example",
22
+ "data": "AAECAwQFBgcICQoLDA0ODw==",
23
+ "encoding": "base64"
24
+ },
25
+ "encrypted": {
26
+ "name": "example",
27
+ "data": "HO4cYSP8LybPYBPZPHQOtuB3dfKG08yw7J4qx3kkjxdW0eoZv+nGAp76OKqYQ327",
28
+ "encoding": "cipher+aes-128-cbc/base64"
29
+ }
30
+ },
31
+ {
32
+ "encoded": {
33
+ "name": "example",
34
+ "data": "{\"example\":{\"json\":\"Object\"}}",
35
+ "encoding": "json"
36
+ },
37
+ "encrypted": {
38
+ "name": "example",
39
+ "data": "HO4cYSP8LybPYBPZPHQOtuD53yrD3YV3NBoTEYBh4U0N1QXHbtkfsDfTspKeLQFt",
40
+ "encoding": "json/utf-8/cipher+aes-128-cbc/base64"
41
+ }
42
+ },
43
+ {
44
+ "encoded": {
45
+ "name": "example",
46
+ "data": "[\"example\",\"json\",\"array\"]",
47
+ "encoding": "json"
48
+ },
49
+ "encrypted": {
50
+ "name": "example",
51
+ "data": "HO4cYSP8LybPYBPZPHQOtvmStzmExkdjvrn51J6cmaTZrGl+EsJ61sgxmZ6j6jcA",
52
+ "encoding": "json/utf-8/cipher+aes-128-cbc/base64"
53
+ }
54
+ }
55
+ ]
56
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "algorithm": "aes",
3
+ "mode": "cbc",
4
+ "keylength": 256,
5
+ "key": "o9qXZoPGDNla50VnRwH7cGqIrpyagTxGsRgimKJbY40=",
6
+ "iv": "NMDl1Acnel8HVdu1cEWdrw==",
7
+ "items": [
8
+ {
9
+ "encoded": {
10
+ "name": "example",
11
+ "data": "The quick brown fox jumped over the lazy dog"
12
+ },
13
+ "encrypted": {
14
+ "name": "example",
15
+ "data": "NMDl1Acnel8HVdu1cEWdr9CGPYFoBoLgJCzoybbQbnyfwx3UQ8CGuKyP/g56Za/JB3xW6XGkNzrHYvZwad4fvA==",
16
+ "encoding": "utf-8/cipher+aes-256-cbc/base64"
17
+ }
18
+ },
19
+ {
20
+ "encoded": {
21
+ "name": "example",
22
+ "data": "AAECAwQFBgcICQoLDA0ODw==",
23
+ "encoding": "base64"
24
+ },
25
+ "encrypted": {
26
+ "name": "example",
27
+ "data": "NMDl1Acnel8HVdu1cEWdr8UFEi56Ms0zPHszbppM61BC8Yf6ndq+kiCj9xXW97/O",
28
+ "encoding": "cipher+aes-256-cbc/base64"
29
+ }
30
+ },
31
+ {
32
+ "encoded": {
33
+ "name": "example",
34
+ "data": "{\"example\":{\"json\":\"Object\"}}",
35
+ "encoding": "json"
36
+ },
37
+ "encrypted": {
38
+ "name": "example",
39
+ "data": "NMDl1Acnel8HVdu1cEWdr21pS5//hdtQf3QqQzZM/jWAtn09Vh52E6jMdC3mWS98",
40
+ "encoding": "json/utf-8/cipher+aes-256-cbc/base64"
41
+ }
42
+ },
43
+ {
44
+ "encoded": {
45
+ "name": "example",
46
+ "data": "[\"example\",\"json\",\"array\"]",
47
+ "encoding": "json"
48
+ },
49
+ "encrypted": {
50
+ "name": "example",
51
+ "data": "NMDl1Acnel8HVdu1cEWdr4J5sVAFpnXsz0fTtsuwOaTRU+6P5GaWlNjePWwiOZCQ",
52
+ "encoding": "json/utf-8/cipher+aes-256-cbc/base64"
53
+ }
54
+ }
55
+ ]
56
+ }
@@ -0,0 +1,39 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ RSpec.configure do |config|
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ config.mock_with :rspec do |mocks|
13
+ # This option should be set when all dependencies are being loaded
14
+ # before a spec run, as is the case in a typical spec helper. It will
15
+ # cause any verifying double instantiation for a class that does not
16
+ # exist to raise, protecting against incorrectly spelt names.
17
+ mocks.verify_doubled_constant_names = true
18
+ end
19
+
20
+ # Run specs in random order to surface order dependencies. If you find an
21
+ # order dependency and want to debug it, you can fix the order by providing
22
+ # the seed, which is printed after each run.
23
+ # --seed 1234
24
+ config.order = 'random'
25
+
26
+ config.before(:example) do
27
+ WebMock.disable!
28
+ end
29
+
30
+ config.before(:example, :webmock => true) do
31
+ allow(TestApp).to receive(:instance).and_return(instance_double('TestApp',
32
+ app_id: 'app_id',
33
+ key_id: 'app_id.key_id',
34
+ api_key: 'app_id.key_id:secret',
35
+ environment: 'sandbox'
36
+ ))
37
+ WebMock.enable!
38
+ end
39
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,46 +1,8 @@
1
- # This file was generated by the `rspec --init` command. Conventionally, all
2
- # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
- # Require this file using `require "spec_helper"` to ensure that it is only
4
- # loaded once.
5
- #
6
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
-
8
1
  require 'webmock/rspec'
9
2
 
10
- require "ably"
11
-
12
- require "support/api_helper"
13
- require "support/event_machine_helper"
14
-
15
- RSpec.configure do |config|
16
- config.run_all_when_everything_filtered = true
17
- config.filter_run :focus
18
-
19
- config.mock_with :rspec do |mocks|
20
- # This option should be set when all dependencies are being loaded
21
- # before a spec run, as is the case in a typical spec helper. It will
22
- # cause any verifying double instantiation for a class that does not
23
- # exist to raise, protecting against incorrectly spelt names.
24
- mocks.verify_doubled_constant_names = true
25
- end
26
-
27
- # Run specs in random order to surface order dependencies. If you find an
28
- # order dependency and want to debug it, you can fix the order by providing
29
- # the seed, which is printed after each run.
30
- # --seed 1234
31
- config.order = 'random'
3
+ require 'ably'
32
4
 
33
- config.before(:example) do
34
- WebMock.disable!
35
- end
5
+ require 'support/api_helper'
6
+ require 'support/event_machine_helper'
36
7
 
37
- config.before(:example, :webmock => true) do
38
- allow(TestApp).to receive(:instance).and_return(instance_double('TestApp',
39
- app_id: 'app_id',
40
- key_id: 'app_id.key_id',
41
- api_key: 'app_id.key_id:secret',
42
- environment: 'sandbox'
43
- ))
44
- WebMock.enable!
45
- end
46
- end
8
+ require 'rspec_config'
@@ -1,4 +1,4 @@
1
- require "support/test_app"
1
+ require 'support/test_app'
2
2
 
3
3
  module ApiHelper
4
4
  def app_id
@@ -6,11 +6,6 @@ module RSpec
6
6
  Timeout::timeout(timeout + 0.5) do
7
7
  EM.run do
8
8
  yield
9
-
10
- EM.add_timer(timeout) do
11
- EM.stop
12
- raise RuntimeError, "EventMachine test did not complete in #{timeout} seconds"
13
- end
14
9
  end
15
10
  end
16
11
  end
@@ -1,6 +1,6 @@
1
1
  shared_examples 'a protocol message bus' do
2
2
  describe '__protocol_msgbus__ PubSub' do
3
- let(:message) do
3
+ let(:protocol_message) do
4
4
  Ably::Models::ProtocolMessage.new(
5
5
  action: 15,
6
6
  channel: 'channel',
@@ -11,8 +11,8 @@ shared_examples 'a protocol message bus' do
11
11
 
12
12
  specify 'supports valid ProtocolMessage messages' do
13
13
  received = 0
14
- msgbus.subscribe(:message) { received += 1 }
15
- expect { msgbus.publish(:message, message) }.to change { received }.to(1)
14
+ msgbus.subscribe(:protocol_message) { received += 1 }
15
+ expect { msgbus.publish(:protocol_message, protocol_message) }.to change { received }.to(1)
16
16
  end
17
17
 
18
18
  specify 'fail with unacceptable STATE event names' do
@@ -1,4 +1,4 @@
1
- require "singleton"
1
+ require 'singleton'
2
2
 
3
3
  class TestApp
4
4
  APP_SPEC = {
@@ -81,8 +81,8 @@ class TestApp
81
81
  url = "#{sandbox_client.endpoint}/apps"
82
82
 
83
83
  headers = {
84
- "Accept" => "application/json",
85
- "Content-Type" => "application/json"
84
+ 'Accept' => 'application/json',
85
+ 'Content-Type' => 'application/json'
86
86
  }
87
87
 
88
88
  response = Faraday.post(url, APP_SPEC.to_json, headers)
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ably::Logger do
4
+ let(:new_client) do
5
+ instance_double('Ably::Realtime::Client', connection: instance_double('Ably::Realtime::Connection', id: nil))
6
+ end
7
+ let(:connected_client) do
8
+ instance_double('Ably::Realtime::Client', connection: instance_double('Ably::Realtime::Connection', id: '0000'))
9
+ end
10
+ let(:rest_client) do
11
+ instance_double('Ably::Rest::Client')
12
+ end
13
+
14
+ subject { Ably::Logger.new(new_client, Logger::INFO) }
15
+
16
+ def uncolorize(string)
17
+ regex_pattern = /\033\[[0-9]+m(.+?)\033\[0m/m
18
+ string.gsub(regex_pattern, '\1')
19
+ end
20
+
21
+ it 'uses the Ruby Logger by default' do
22
+ expect(subject.logger).to be_a(Logger)
23
+ end
24
+
25
+ it 'delegates to the logger object' do
26
+ expect(subject.logger).to receive(:warn).with('message')
27
+ subject.warn 'message'
28
+ end
29
+
30
+ context 'formatter' do
31
+ context 'when debugging' do
32
+ it 'uses short time format' do
33
+ formatted = subject.logger.formatter.call(Logger::DEBUG, Time.now, 'progid', 'unique_message')
34
+ formatted = uncolorize(formatted)
35
+ expect(formatted).to match(/^\d+:\d+:\d+.\d{3} DEBUG/)
36
+ end
37
+ end
38
+
39
+ context 'when info -> fatal' do
40
+ it 'uses long time format' do
41
+ formatted = subject.logger.formatter.call(Logger::INFO, Time.now, 'progid', 'unique_message')
42
+ formatted = uncolorize(formatted)
43
+ expect(formatted).to match(/^\d+-\d+-\d+ \d+:\d+:\d+.\d{3} INFO/)
44
+ end
45
+ end
46
+
47
+ context 'with Realtime disconnected client' do
48
+ subject { Ably::Logger.new(new_client, Logger::INFO) }
49
+
50
+ it 'formats logs with an empty client ID' do
51
+ formatted = subject.logger.formatter.call(Logger::DEBUG, Time.now, 'progid', 'unique_message')
52
+ formatted = uncolorize(formatted)
53
+ expect(formatted).to match(/\[ \-\- \]/)
54
+ expect(formatted).to match(%r{unique_message$})
55
+ expect(formatted).to match(%r{DEBUG})
56
+ end
57
+ end
58
+
59
+ context 'with Realtime connected client' do
60
+ subject { Ably::Logger.new(connected_client, Logger::INFO) }
61
+
62
+ it 'formats logs with a client ID' do
63
+ formatted = subject.logger.formatter.call(Logger::DEBUG, Time.now, 'progid', 'unique_message')
64
+ formatted = uncolorize(formatted)
65
+ expect(formatted).to match(/\[0000]/)
66
+ expect(formatted).to match(%r{unique_message$})
67
+ expect(formatted).to match(%r{DEBUG})
68
+ end
69
+ end
70
+
71
+ context 'with REST client' do
72
+ subject { Ably::Logger.new(rest_client, Logger::INFO) }
73
+
74
+ it 'formats logs without a client ID' do
75
+ formatted = subject.logger.formatter.call(Logger::FATAL, Time.now, 'progid', 'unique_message')
76
+ formatted = uncolorize(formatted)
77
+ expect(formatted).to_not match(/\[.*\]/)
78
+ expect(formatted).to match(%r{unique_message$})
79
+ expect(formatted).to match(%r{FATAL})
80
+ end
81
+ end
82
+
83
+ context 'severity argument' do
84
+ it 'can be an Integer' do
85
+ formatted = subject.logger.formatter.call(Logger::INFO, Time.now, 'progid', 'unique_message')
86
+ formatted = uncolorize(formatted)
87
+ expect(formatted).to match(/^\d+-\d+-\d+ \d+:\d+:\d+.\d{3} INFO/)
88
+ end
89
+
90
+ it 'can be a string' do
91
+ formatted = subject.logger.formatter.call('INFO', Time.now, 'progid', 'unique_message')
92
+ formatted = uncolorize(formatted)
93
+ expect(formatted).to match(/^\d+-\d+-\d+ \d+:\d+:\d+.\d{3} INFO/)
94
+ end
95
+ end
96
+ end
97
+
98
+ context 'with a custom Logger' do
99
+ context 'with an invalid interface' do
100
+ let(:custom_logger_with_bad_interface) do
101
+ Class.new.new
102
+ end
103
+ subject { Ably::Logger.new(new_client, Logger::INFO, custom_logger_with_bad_interface) }
104
+
105
+ it 'raises an exception' do
106
+ expect { subject }.to raise_error ArgumentError, /The custom Logger's interface does not provide the method/
107
+ end
108
+ end
109
+
110
+ context 'with a valid interface' do
111
+ let(:custom_logger) do
112
+ Class.new do
113
+ extend Forwardable
114
+ def initialize
115
+ @logger = Logger.new(STDOUT)
116
+ end
117
+ def_delegators :@logger, :fatal, :error, :warn, :info, :debug, :level, :level=
118
+ end
119
+ end
120
+ let(:custom_logger_object) { custom_logger.new }
121
+
122
+ subject { Ably::Logger.new(new_client, Logger::INFO, custom_logger_object) }
123
+
124
+ it 'is used' do
125
+ expect { subject }.to_not raise_error
126
+ expect(subject.logger.class).to eql(custom_logger)
127
+ end
128
+
129
+ it 'delegates log messages to logger' do
130
+ expect(custom_logger_object).to receive(:fatal).with('message')
131
+ subject.fatal 'message'
132
+ end
133
+ end
134
+ end
135
+ end