stomper 1.0.0 → 2.0.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 (152) hide show
  1. data/.gitignore +5 -0
  2. data/{spec/spec.opts → .rspec} +0 -2
  3. data/Gemfile +4 -0
  4. data/LICENSE +201 -201
  5. data/README.md +130 -0
  6. data/Rakefile +5 -0
  7. data/examples/basic.rb +38 -0
  8. data/examples/events.rb +54 -0
  9. data/features/acking_messages.feature +147 -0
  10. data/features/disconnecting.feature +12 -0
  11. data/features/establish_connection.feature +44 -0
  12. data/features/protocol_version_negotiation.feature +61 -0
  13. data/features/receipts.feature +72 -0
  14. data/features/scopes.feature +32 -0
  15. data/features/secure_connections.feature +38 -0
  16. data/features/send_and_message.feature +28 -0
  17. data/features/steps/acking_messages_steps.rb +39 -0
  18. data/features/steps/disconnecting_steps.rb +8 -0
  19. data/features/steps/establish_connection_steps.rb +74 -0
  20. data/features/steps/frame_transmission_steps.rb +35 -0
  21. data/features/steps/protocol_version_negotiation_steps.rb +15 -0
  22. data/features/steps/receipts_steps.rb +79 -0
  23. data/features/steps/scopes_steps.rb +52 -0
  24. data/features/steps/secure_connections_steps.rb +41 -0
  25. data/features/steps/send_and_message_steps.rb +35 -0
  26. data/features/steps/subscribing_steps.rb +36 -0
  27. data/features/steps/threaded_receiver_steps.rb +8 -0
  28. data/features/steps/transactions_steps.rb +0 -0
  29. data/features/subscribing.feature +151 -0
  30. data/features/support/env.rb +11 -0
  31. data/features/support/header_helpers.rb +12 -0
  32. data/features/support/ssl/README +6 -0
  33. data/features/support/ssl/broker_cert.csr +17 -0
  34. data/features/support/ssl/broker_cert.pem +72 -0
  35. data/features/support/ssl/broker_key.pem +27 -0
  36. data/features/support/ssl/client_cert.csr +17 -0
  37. data/features/support/ssl/client_cert.pem +72 -0
  38. data/features/support/ssl/client_key.pem +27 -0
  39. data/features/support/ssl/demoCA/cacert.pem +17 -0
  40. data/features/support/ssl/demoCA/index.txt +2 -0
  41. data/features/support/ssl/demoCA/index.txt.attr +1 -0
  42. data/features/support/ssl/demoCA/index.txt.attr.old +1 -0
  43. data/features/support/ssl/demoCA/index.txt.old +1 -0
  44. data/features/support/ssl/demoCA/newcerts/01.pem +72 -0
  45. data/features/support/ssl/demoCA/newcerts/02.pem +72 -0
  46. data/features/support/ssl/demoCA/private/cakey.pem +17 -0
  47. data/features/support/ssl/demoCA/serial +1 -0
  48. data/features/support/ssl/demoCA/serial.old +1 -0
  49. data/features/support/test_stomp_server.rb +150 -0
  50. data/features/threaded_receiver.feature +11 -0
  51. data/features/transactions.feature +66 -0
  52. data/lib/stomper.rb +30 -20
  53. data/lib/stomper/connection.rb +442 -102
  54. data/lib/stomper/errors.rb +59 -0
  55. data/lib/stomper/extensions.rb +10 -0
  56. data/lib/stomper/extensions/common.rb +258 -0
  57. data/lib/stomper/extensions/events.rb +213 -0
  58. data/lib/stomper/extensions/heartbeat.rb +101 -0
  59. data/lib/stomper/extensions/scoping.rb +56 -0
  60. data/lib/stomper/frame.rb +54 -0
  61. data/lib/stomper/frame_serializer.rb +217 -0
  62. data/lib/stomper/headers.rb +15 -0
  63. data/lib/stomper/receipt_manager.rb +36 -0
  64. data/lib/stomper/receivers.rb +7 -0
  65. data/lib/stomper/receivers/threaded.rb +71 -0
  66. data/lib/stomper/scopes.rb +9 -0
  67. data/lib/stomper/scopes/header_scope.rb +49 -0
  68. data/lib/stomper/scopes/receipt_scope.rb +44 -0
  69. data/lib/stomper/scopes/transaction_scope.rb +109 -0
  70. data/lib/stomper/sockets.rb +66 -28
  71. data/lib/stomper/subscription_manager.rb +79 -0
  72. data/lib/stomper/support.rb +68 -0
  73. data/lib/stomper/support/1.8/frame_serializer.rb +53 -0
  74. data/lib/stomper/support/1.8/headers.rb +183 -0
  75. data/lib/stomper/support/1.9/frame_serializer.rb +64 -0
  76. data/lib/stomper/support/1.9/headers.rb +172 -0
  77. data/lib/stomper/support/ruby.rb +13 -0
  78. data/lib/stomper/uris.rb +49 -0
  79. data/lib/stomper/version.rb +7 -0
  80. data/spec/spec_helper.rb +13 -9
  81. data/spec/stomper/connection_spec.rb +712 -0
  82. data/spec/stomper/extensions/common_spec.rb +187 -0
  83. data/spec/stomper/extensions/events_spec.rb +78 -0
  84. data/spec/stomper/extensions/heartbeat_spec.rb +103 -0
  85. data/spec/stomper/extensions/scoping_spec.rb +21 -0
  86. data/spec/stomper/frame_serializer_1.8_spec.rb +318 -0
  87. data/spec/stomper/frame_serializer_spec.rb +316 -0
  88. data/spec/stomper/frame_spec.rb +36 -0
  89. data/spec/stomper/headers_spec.rb +224 -0
  90. data/spec/stomper/receipt_manager_spec.rb +91 -0
  91. data/spec/stomper/receivers/threaded_spec.rb +116 -0
  92. data/spec/stomper/scopes/header_scope_spec.rb +42 -0
  93. data/spec/stomper/scopes/receipt_scope_spec.rb +51 -0
  94. data/spec/stomper/scopes/transaction_scope_spec.rb +183 -0
  95. data/spec/stomper/sockets_spec.rb +113 -0
  96. data/spec/stomper/subscription_manager_spec.rb +107 -0
  97. data/spec/stomper/support_spec.rb +69 -0
  98. data/spec/stomper/uris_spec.rb +54 -0
  99. data/spec/stomper_spec.rb +9 -0
  100. data/spec/support/custom_argument_matchers.rb +57 -0
  101. data/spec/support/existential_frame_matchers.rb +19 -0
  102. data/spec/support/frame_header_matchers.rb +10 -0
  103. data/stomper.gemspec +30 -0
  104. metadata +272 -97
  105. data/AUTHORS +0 -21
  106. data/CHANGELOG +0 -20
  107. data/README.rdoc +0 -120
  108. data/lib/stomper/client.rb +0 -34
  109. data/lib/stomper/frame_reader.rb +0 -73
  110. data/lib/stomper/frame_writer.rb +0 -21
  111. data/lib/stomper/frames.rb +0 -39
  112. data/lib/stomper/frames/abort.rb +0 -10
  113. data/lib/stomper/frames/ack.rb +0 -25
  114. data/lib/stomper/frames/begin.rb +0 -11
  115. data/lib/stomper/frames/client_frame.rb +0 -89
  116. data/lib/stomper/frames/commit.rb +0 -10
  117. data/lib/stomper/frames/connect.rb +0 -10
  118. data/lib/stomper/frames/connected.rb +0 -30
  119. data/lib/stomper/frames/disconnect.rb +0 -10
  120. data/lib/stomper/frames/error.rb +0 -21
  121. data/lib/stomper/frames/message.rb +0 -48
  122. data/lib/stomper/frames/receipt.rb +0 -19
  123. data/lib/stomper/frames/send.rb +0 -10
  124. data/lib/stomper/frames/server_frame.rb +0 -38
  125. data/lib/stomper/frames/subscribe.rb +0 -42
  126. data/lib/stomper/frames/unsubscribe.rb +0 -19
  127. data/lib/stomper/open_uri_interface.rb +0 -41
  128. data/lib/stomper/receipt_handlers.rb +0 -23
  129. data/lib/stomper/receiptor.rb +0 -38
  130. data/lib/stomper/subscriber.rb +0 -76
  131. data/lib/stomper/subscription.rb +0 -128
  132. data/lib/stomper/subscriptions.rb +0 -95
  133. data/lib/stomper/threaded_receiver.rb +0 -59
  134. data/lib/stomper/transaction.rb +0 -185
  135. data/lib/stomper/transactor.rb +0 -50
  136. data/lib/stomper/uri.rb +0 -55
  137. data/spec/client_spec.rb +0 -29
  138. data/spec/connection_spec.rb +0 -22
  139. data/spec/frame_reader_spec.rb +0 -37
  140. data/spec/frame_writer_spec.rb +0 -27
  141. data/spec/frames/client_frame_spec.rb +0 -66
  142. data/spec/frames/indirect_frame_spec.rb +0 -45
  143. data/spec/frames/server_frame_spec.rb +0 -85
  144. data/spec/open_uri_interface_spec.rb +0 -132
  145. data/spec/receiptor_spec.rb +0 -35
  146. data/spec/shared_connection_examples.rb +0 -79
  147. data/spec/subscriber_spec.rb +0 -77
  148. data/spec/subscription_spec.rb +0 -157
  149. data/spec/subscriptions_spec.rb +0 -145
  150. data/spec/threaded_receiver_spec.rb +0 -33
  151. data/spec/transaction_spec.rb +0 -139
  152. data/spec/transactor_spec.rb +0 -46
@@ -0,0 +1,187 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ module Stomper::Extensions
5
+ describe Common do
6
+ before(:each) do
7
+ @common = mock("common", :version => '1.0')
8
+ @common.extend Common
9
+ @subscription_manager = mock('subscription manager')
10
+ @common.stub!(:subscription_manager).and_return(@subscription_manager)
11
+ end
12
+
13
+ describe "Shared default interface" do
14
+ before(:each) do
15
+ @common.stub!(:is_a?).with(::Stomper::Connection).and_return(true)
16
+ end
17
+ it "should transmit an ABORT frame" do
18
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'transaction' => 'tx-1234'}, 'ABORT'))
19
+ @common.abort('tx-1234')
20
+ end
21
+
22
+ it "should transmit a BEGIN frame" do
23
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'transaction' => 'tx-3124'}, 'BEGIN'))
24
+ @common.begin('tx-3124')
25
+ end
26
+
27
+ it "should transmit a COMMIT frame" do
28
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'transaction' => 'tx-4321'}, 'COMMIT'))
29
+ @common.commit('tx-4321')
30
+ end
31
+
32
+ it "should transmit a SEND frame with :send" do
33
+ @common.should_receive(:transmit).with(stomper_frame('testing :send', {'destination' => '/queue/test_send'}, 'SEND'))
34
+ @common.send('/queue/test_send', 'testing :send')
35
+ end
36
+
37
+ it "should transmit a SEND frame with :puts" do
38
+ @common.should_receive(:transmit).with(stomper_frame('testing :puts', {'destination' => '/queue/test_puts'}, 'SEND'))
39
+ @common.puts('/queue/test_puts', 'testing :puts')
40
+ end
41
+
42
+ it "should transmit a SUBSCRIBE frame" do
43
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'destination' => '/queue/test_subscribe'}, 'SUBSCRIBE'))
44
+ @common.subscribe('/queue/test_subscribe')
45
+ end
46
+
47
+ it "should transmit an UNSUBSCRIBE frame for a given subscription ID" do
48
+ @subscription_manager.should_receive(:subscribed_id?).with('subscription-1234').and_return(true)
49
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'id' => 'subscription-1234'}, 'UNSUBSCRIBE'))
50
+ @common.unsubscribe('subscription-1234')
51
+ end
52
+
53
+ it "should transmit an UNSUBSCRIBE frame for a given SUBSCRIBE frame" do
54
+ @subscription_manager.should_receive(:subscribed_id?).with('id-in-frame-4321').and_return(true)
55
+ subscribe = ::Stomper::Frame.new('SUBSCRIBE', { :id => 'id-in-frame-4321' })
56
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'id' => 'id-in-frame-4321'}, 'UNSUBSCRIBE'))
57
+ @common.unsubscribe(subscribe)
58
+ end
59
+
60
+ it "should raise an error on :nack" do
61
+ lambda { @common.nack("msg-001", "sub-001") }.should raise_error
62
+ end
63
+
64
+ it "should transmit an ACK for a message-id given MESSAGE frame" do
65
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'message-id' => 'msg-001'}, 'ACK'))
66
+ @common.ack(Stomper::Frame.new('MESSAGE', { :'message-id' => 'msg-001' }, 'some body'))
67
+ end
68
+
69
+ it "should transmit an ACK for a message-id given a message-id" do
70
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'message-id' => 'msg-123'}, 'ACK'))
71
+ @common.ack('msg-123')
72
+ end
73
+
74
+ it "should raise an error when the message-id cannot be inferred" do
75
+ lambda { @common.ack('') }.should raise_error(ArgumentError)
76
+ lambda { @common.ack(Stomper::Frame.new('MESSAGE', { :id => '' }, 'some body')) }.should raise_error(ArgumentError)
77
+ lambda { @common.ack(nil) }.should raise_error(ArgumentError)
78
+ lambda { @common.ack(Stomper::Frame.new('MESSAGE', {}, 'some body')) }.should raise_error(ArgumentError)
79
+ end
80
+ end
81
+
82
+ describe "subscription handling" do
83
+ it "should add a callback to the subscription manager on :subscribe" do
84
+ # In this tests, we do not care about the particulars of the generated
85
+ # frame. Our only interest is the interaction with the subscription
86
+ # manager
87
+ @common.stub!(:transmit).and_return { |f| f }
88
+ @subscription_manager.should_receive(:add).with(stomper_frame_with_headers({}, 'SUBSCRIBE'), an_instance_of(Proc))
89
+ @common.subscribe('/queue/test') { |m| true }
90
+ end
91
+ it "should unsubscribe by destination" do
92
+ # As this invocation of unsubscribe will result in multiple UNSUBSCRIBE
93
+ # frames being generated, we do care about the actual frames generated
94
+ # as well.
95
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({:id => '1234'}, 'UNSUBSCRIBE'))
96
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({:id => '4567'}, 'UNSUBSCRIBE'))
97
+ @subscription_manager.should_receive(:subscribed_id?).with('/queue/test').and_return(false)
98
+ @subscription_manager.should_receive(:subscribed_destination?).with('/queue/test').and_return(true)
99
+ @subscription_manager.should_receive(:ids_for_destination).with('/queue/test').and_return(['1234', '4567'])
100
+ @common.unsubscribe("/queue/test")
101
+ end
102
+ end
103
+
104
+ describe "SEND receipt handling" do
105
+ it "should build a receipt scope when a block is passed to :send" do
106
+ receipt_scope = mock('receipt scope')
107
+ @common.should_receive(:with_receipt).and_return(receipt_scope)
108
+ receipt_scope.should_receive(:transmit).with(stomper_frame('my message', { :destination => '/topic/testing' }, 'SEND'))
109
+ @common.send('/topic/testing', 'my message') { |r| true }
110
+ end
111
+
112
+ it "should build a receipt scope when a block is passed to :puts" do
113
+ receipt_scope = mock('receipt scope')
114
+ @common.should_receive(:with_receipt).and_return(receipt_scope)
115
+ receipt_scope.should_receive(:transmit).with(stomper_frame('my message', { :destination => '/topic/testing' }, 'SEND'))
116
+ @common.puts('/topic/testing', 'my message') { |r| true }
117
+ end
118
+ end
119
+
120
+ describe "1.1 Protocol Extensions" do
121
+ before(:each) do
122
+ @common.stub!(:version).and_return('1.1')
123
+ Common.extend_by_protocol_version(@common, '1.1')
124
+ end
125
+ it "should include V1_1 module" do
126
+ @common.should be_a_kind_of(Common::V1_1)
127
+ end
128
+
129
+ it "should transmit a NACK for a message-id and subscription given MESSAGE frame" do
130
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'message-id' => 'msg-456', 'subscription' => 'sub-123'}, 'NACK'))
131
+ @common.nack(Stomper::Frame.new('MESSAGE', { :'message-id' => 'msg-456', :subscription => 'sub-123' }, 'some body'))
132
+ end
133
+ it "should transmit a NACK for a message-id and subscription given MESSAGE frame w/o subscription and subscription-id" do
134
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'message-id' => 'msg-456', 'subscription' => 'sub-123'}, 'NACK'))
135
+ @common.nack(Stomper::Frame.new('MESSAGE', { :'message-id' => 'msg-456', :subscription => '' }, 'some body'), 'sub-123')
136
+ end
137
+ it "should transmit a NACK for a message-id and subscription given message-id and subscription-id" do
138
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'message-id' => 'msg-456', 'subscription' => 'sub-123'}, 'NACK'))
139
+ @common.nack('msg-456', 'sub-123')
140
+ end
141
+ it "should raise an error when the subscription ID cannot be inferred" do
142
+ lambda { @common.nack('msg-456') }.should raise_error(ArgumentError)
143
+ lambda { @common.nack(Stomper::Frame.new('MESSAGE', { :'message-id' => 'msg-456' }, 'some body')) }.should raise_error(ArgumentError)
144
+ lambda { @common.nack('msg-456', '') }.should raise_error(ArgumentError)
145
+ lambda { @common.nack(Stomper::Frame.new('MESSAGE', { :'message-id' => 'msg-456' }, 'some body'), '') }.should raise_error(ArgumentError)
146
+ lambda { @common.nack('msg-456', { :subscription => ''}) }.should raise_error(ArgumentError)
147
+ lambda { @common.nack(Stomper::Frame.new('MESSAGE', { :'message-id' => 'msg-456' }, 'some body'), '', { :subscription => nil }) }.should raise_error(ArgumentError)
148
+ lambda { @common.nack('msg-456', { :subscription => 'sub-123'}) }.should raise_error(ArgumentError)
149
+ end
150
+ it "should raise an error when the message-id cannot be inferred" do
151
+ lambda { @common.nack('', 'sub-123') }.should raise_error(ArgumentError)
152
+ lambda { @common.nack(Stomper::Frame.new('MESSAGE', { :'message-id' => '' }, 'some body'), 'sub-123') }.should raise_error(ArgumentError)
153
+ lambda { @common.nack(nil, 'sub-123') }.should raise_error(ArgumentError)
154
+ lambda { @common.nack(Stomper::Frame.new('MESSAGE', {}, 'some body'), 'sub-123') }.should raise_error(ArgumentError)
155
+ end
156
+
157
+ it "should transmit an ACK for a message-id and subscription given MESSAGE frame" do
158
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'message-id' => 'msg-456', 'subscription' => 'sub-123'}, 'ACK'))
159
+ @common.ack(Stomper::Frame.new('MESSAGE', { :'message-id' => 'msg-456', :subscription => 'sub-123' }, 'some body'))
160
+ end
161
+ it "should transmit an ACK for a message-id and subscription given MESSAGE frame w/o subscription and subscription-id" do
162
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'message-id' => 'msg-456', 'subscription' => 'sub-123'}, 'ACK'))
163
+ @common.ack(Stomper::Frame.new('MESSAGE', { :'message-id' => 'msg-456', :subscription => '' }, 'some body'), 'sub-123')
164
+ end
165
+ it "should transmit an ACK for a message-id and subscription given message-id and subscription-id" do
166
+ @common.should_receive(:transmit).with(stomper_frame_with_headers({'message-id' => 'msg-456', 'subscription' => 'sub-123'}, 'ACK'))
167
+ @common.ack('msg-456', 'sub-123')
168
+ end
169
+ it "should raise an error when the subscription ID cannot be inferred" do
170
+ lambda { @common.ack('msg-456') }.should raise_error(ArgumentError)
171
+ lambda { @common.ack(Stomper::Frame.new('MESSAGE', { :'message-id' => 'msg-456' }, 'some body')) }.should raise_error(ArgumentError)
172
+ lambda { @common.ack('msg-456', '') }.should raise_error(ArgumentError)
173
+ lambda { @common.ack(Stomper::Frame.new('MESSAGE', { :'message-id' => 'msg-456' }, 'some body'), '') }.should raise_error(ArgumentError)
174
+ lambda { @common.ack('msg-456', { :subscription => ''}) }.should raise_error(ArgumentError)
175
+ lambda { @common.ack(Stomper::Frame.new('MESSAGE', { :'message-id' => 'msg-456' }, 'some body'), '', { :subscription => nil }) }.should raise_error(ArgumentError)
176
+ lambda { @common.ack('msg-456', { :subscription => 'sub-123'}) }.should raise_error(ArgumentError)
177
+ end
178
+ it "should raise an error when the message-id cannot be inferred" do
179
+ lambda { @common.ack('', 'sub-123') }.should raise_error(ArgumentError)
180
+ lambda { @common.ack(Stomper::Frame.new('MESSAGE', { :'message-id' => '' }, 'some body'), 'sub-123') }.should raise_error(ArgumentError)
181
+ lambda { @common.ack(nil, 'sub-123') }.should raise_error(ArgumentError)
182
+ lambda { @common.ack(Stomper::Frame.new('MESSAGE', {}, 'some body'), 'sub-123') }.should raise_error(ArgumentError)
183
+ lambda { @common.ack('', { :'message-id' => 'msg-456', :subscription => 'sub-123'}) }.should raise_error(ArgumentError)
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,78 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ module Stomper::Extensions
5
+ describe Events do
6
+ before(:each) do
7
+ @events = mock('events')
8
+ @events.extend Events
9
+ end
10
+
11
+ describe "basic event callbacks" do
12
+ [:on_abort, :on_ack, :on_begin, :on_commit, :on_connect, :on_stomp,
13
+ :on_connected, :on_disconnect, :on_error, :on_message, :on_nack,
14
+ :on_receipt, :on_send, :on_subscribe, :on_unsubscribe, :on_client_beat,
15
+ :on_broker_beat, :on_connection_established, :on_connection_closed,
16
+ :on_connection_terminated, :on_connection_disconnected,
17
+ :on_connection_died, :before_transmitting, :after_transmitting,
18
+ :before_receiving, :after_receiving].each do |event_name|
19
+
20
+ it "should register a callback for #{event_name} and trigger appropriately" do
21
+ triggered = false
22
+ @events.__send__(event_name) do
23
+ triggered = true
24
+ end
25
+ @events.__send__(:trigger_event, event_name)
26
+ triggered.should be_true
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "frame event callbacks" do
32
+ [ :on_connected, :on_error, :on_message, :on_receipt].each do |event_name|
33
+
34
+ it "should register a callback for #{event_name} and trigger when the frame is received" do
35
+ command_name = event_name.to_s.split('_').last.upcase
36
+ triggered = false
37
+ @events.__send__(event_name) do
38
+ triggered = true
39
+ end
40
+ @events.__send__(:trigger_received_frame, ::Stomper::Frame.new(command_name))
41
+ triggered.should be_true
42
+ end
43
+ end
44
+
45
+ [:on_abort, :on_ack, :on_begin, :on_commit, :on_connect, :on_stomp,
46
+ :on_disconnect, :on_nack, :on_send, :on_subscribe, :on_unsubscribe].each do |event_name|
47
+
48
+ it "should register a callback for #{event_name} and trigger when the frame is transmitted" do
49
+ command_name = event_name.to_s.split('_').last.upcase
50
+ triggered = false
51
+ @events.__send__(event_name) do
52
+ triggered = true
53
+ end
54
+ @events.__send__(:trigger_transmitted_frame, ::Stomper::Frame.new(command_name))
55
+ triggered.should be_true
56
+ end
57
+ end
58
+
59
+ it "should trigger a broker beat when receiving a frame with no command" do
60
+ triggered = false
61
+ @events.on_broker_beat do
62
+ triggered = true
63
+ end
64
+ @events.__send__(:trigger_received_frame, ::Stomper::Frame.new)
65
+ triggered.should be_true
66
+ end
67
+
68
+ it "should trigger a client beat when receiving a frame with no command" do
69
+ triggered = false
70
+ @events.on_client_beat do
71
+ triggered = true
72
+ end
73
+ @events.__send__(:trigger_transmitted_frame, ::Stomper::Frame.new)
74
+ triggered.should be_true
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,103 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ module Stomper::Extensions
5
+ describe Heartbeat do
6
+ before(:each) do
7
+ @heartbeats = mock('heartbeats')
8
+ @heartbeats.extend Heartbeat
9
+ end
10
+
11
+ describe "Shared default interface" do
12
+ it "should be dead? when it is not alive?" do
13
+ @heartbeats.stub(:alive? => true)
14
+ @heartbeats.dead?.should be_false
15
+
16
+ @heartbeats.stub(:alive? => false)
17
+ @heartbeats.dead?.should be_true
18
+ end
19
+
20
+ it "should do nothing when :beat is called" do
21
+ @heartbeats.beat.should be_nil
22
+ end
23
+
24
+ it "should be alive if the connection is connected?" do
25
+ @heartbeats.should_receive(:connected?).and_return(true)
26
+ @heartbeats.alive?.should be_true
27
+ @heartbeats.should_receive(:connected?).and_return(false)
28
+ @heartbeats.alive?.should be_false
29
+ end
30
+ end
31
+
32
+ describe "1.1 Protocol Extensions" do
33
+ before(:each) do
34
+ Heartbeat.extend_by_protocol_version(@heartbeats, '1.1')
35
+ end
36
+
37
+ it "should include the 1.1 extensions" do
38
+ @heartbeats.should be_a_kind_of(Heartbeat::V1_1)
39
+ end
40
+
41
+ it "should transmit a heartbeat frame through :beat" do
42
+ @heartbeats.should_receive(:transmit).with(stomper_heartbeat_frame)
43
+ @heartbeats.beat
44
+ end
45
+
46
+ it "should be alive if client and broker are alive and connected" do
47
+ @heartbeats.stub(:client_alive? => true, :broker_alive? => true, :connected? => true)
48
+ @heartbeats.alive?.should be_true
49
+ @heartbeats.stub(:connected? => false)
50
+ @heartbeats.alive?.should be_false
51
+ end
52
+
53
+
54
+ it "should be dead if connected and broker is alive but client is not" do
55
+ @heartbeats.stub(:client_alive? => false, :broker_alive? => true, :connected? => true)
56
+ @heartbeats.alive?.should be_false
57
+ @heartbeats.stub(:client_alive? => true)
58
+ @heartbeats.alive?.should be_true
59
+ end
60
+
61
+ it "should be dead if connected and client is alive but broker is not" do
62
+ @heartbeats.stub(:client_alive? => true, :broker_alive? => false, :connected? => true)
63
+ @heartbeats.alive?.should be_false
64
+ @heartbeats.stub(:broker_alive? => true)
65
+ @heartbeats.alive?.should be_true
66
+ end
67
+
68
+ it "should have a living client if client beats are disabled" do
69
+ @heartbeats.stub(:heartbeating => [0, 10], :connected? => true)
70
+ @heartbeats.client_alive?.should be_true
71
+ end
72
+
73
+ it "should have a living broker if broker beats are disabled" do
74
+ @heartbeats.stub(:heartbeating => [10, 0], :connected? => true)
75
+ @heartbeats.broker_alive?.should be_true
76
+ end
77
+
78
+ it "should have a living client if beating is enabled and transmitted within a marin of error" do
79
+ @heartbeats.stub(:heartbeating => [1_000, 0], :connected? => true)
80
+ @heartbeats.stub(:duration_since_transmitted => 50)
81
+ @heartbeats.client_alive?.should be_true
82
+ @heartbeats.stub(:duration_since_transmitted => 1_000)
83
+ @heartbeats.client_alive?.should be_true
84
+ @heartbeats.stub(:duration_since_transmitted => 1_100)
85
+ @heartbeats.client_alive?.should be_true
86
+ @heartbeats.stub(:duration_since_transmitted => 1_200)
87
+ @heartbeats.client_alive?.should be_false
88
+ end
89
+
90
+ it "should have a living broker if beating is enabled and received within a marin of error" do
91
+ @heartbeats.stub(:heartbeating => [0, 5_000], :connected? => true)
92
+ @heartbeats.stub(:duration_since_received => 100)
93
+ @heartbeats.broker_alive?.should be_true
94
+ @heartbeats.stub(:duration_since_received => 5_000)
95
+ @heartbeats.broker_alive?.should be_true
96
+ @heartbeats.stub(:duration_since_received => 5_500)
97
+ @heartbeats.broker_alive?.should be_true
98
+ @heartbeats.stub(:duration_since_received => 5_600)
99
+ @heartbeats.broker_alive?.should be_false
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ module Stomper::Extensions
5
+ describe Scoping do
6
+ before(:each) do
7
+ @scoping = mock("scoping", :version => '1.0')
8
+ @scoping.extend Scoping
9
+ end
10
+
11
+ it "should return a new HeaderScope" do
12
+ @scoping.with_headers({}).should be_an_instance_of(::Stomper::Scopes::HeaderScope)
13
+ end
14
+ it "should return a new TransactionScope" do
15
+ @scoping.with_transaction.should be_an_instance_of(::Stomper::Scopes::TransactionScope)
16
+ end
17
+ it "should return a new ReceiptScope" do
18
+ @scoping.with_receipt.should be_an_instance_of(::Stomper::Scopes::ReceiptScope)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,318 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ module Stomper
5
+ if RUBY_VERSION < '1.9'
6
+ describe FrameSerializer do
7
+ before(:each) do
8
+ @messages = {
9
+ :content_type_and_charset => "MESSAGE\ncontent-type:text/plain; charset=ISO-8859-1\ncontent-length:6\na-header: padded \n\nh\xEBllo!\000",
10
+ :escaped_headers => "MESSAGE\ncontent-type:text/plain;charset=UTF-8\ncontent-length:7\na\\nspecial\\chead\\\\cer: padded\\c and using\\nspecial\\\\\\\\\\\\ncharacters \n\nh\xC3\xABllo!\000",
11
+ :no_content_length => "MESSAGE\ncontent-type:text/plain\n\nh\xC3\xABllo!\000",
12
+ :repeated_headers => "MESSAGE\ncontent-type:text/plain\nrepeated header:a value\nrepeated header:alternate value\n\nh\xC3\xABllo!\000",
13
+ :non_text_content_type => "MESSAGE\ncontent-type:not-text/other\n\nh\xC3\xABllo!\000",
14
+ :no_content_type => "MESSAGE\n\nh\xC3\xABllo!\000",
15
+ :invalid_content_length => "MESSAGE\ncontent-length:4\n\n12345\000",
16
+ :invalid_header_character => "MESSAGE\ngrandpa:he was:anti\n\n12345\000",
17
+ :invalid_header_sequence => "MESSAGE\ngrandpa:he was\\ranti\n\n12345\000",
18
+ :malformed_header => "MESSAGE\nearth_below_us\nfloating:weightless\n\n12345\000",
19
+ :dangling_header_sequence => "MESSAGE\ngrandpa:he was anti\\\n\n12345\000",
20
+ }
21
+
22
+ @frames = {
23
+ :common => ::Stomper::Frame.new('FRAME', {}, 'body of message'),
24
+ :no_headers => ::Stomper::Frame.new('FRAME', {}, 'body of message'),
25
+ :no_body => ::Stomper::Frame.new('FRAME'),
26
+ :no_command => ::Stomper::Frame.new,
27
+ :header_name_with_linefeed => ::Stomper::Frame.new('FRAME', { "a\ntest\nheader" => "va\\lue : is\n\nme"}),
28
+ :header_name_with_colon => ::Stomper::Frame.new('FRAME', { "a:test:header" => "va\\lue : is\n\nme"}),
29
+ :header_name_with_backslash => ::Stomper::Frame.new('FRAME', { "a\\test\\header" => "va\\lue : is\n\nme"}),
30
+ :binary_body_no_content_type => ::Stomper::Frame.new('FRAME', {}, 'body of message'),
31
+ :charset_header_text_body => ::Stomper::Frame.new('FRAME', {:'content-type' => 'text/plain; param="value";charset=ISO-8859-1'}, 'body of message'),
32
+ :charset_header_binary_body => ::Stomper::Frame.new('FRAME', {:'content-type' => 'application/pdf; param="value";charset=ISO-8859-1'}, 'body of message')
33
+ }
34
+ #{ :header_1 => 'value 1', :header_2 => '3', :header_3 => '', :'content-type' => 'text/plain'}
35
+ @frames[:common][:header_1] = 'value 1'
36
+ @frames[:common][:header_2] = '3'
37
+ @frames[:common][:header_3] = ''
38
+ @frames[:common][:'content-type'] = 'text/plain'
39
+ # :header_1 => 'val', :musical => '', :offering => '4'
40
+ @frames[:no_body][:header_1] = 'val'
41
+ @frames[:no_body][:musical] = ''
42
+ @frames[:no_body][:offering] = '4'
43
+ @frame_io = StringIO.new
44
+ @frame_serializer = FrameSerializer.new(@frame_io)
45
+ end
46
+
47
+ describe "thread safety" do
48
+ before(:each) do
49
+ @frame_serializer = FrameSerializer.new(mock('frame io'))
50
+ end
51
+ it "should synchronize writing to the underlying IO" do
52
+ first_called = false
53
+ call_next = false
54
+ ordered = []
55
+ @frame_serializer.stub!(:__write_frame__).and_return do |f|
56
+ first_called = true
57
+ ordered << 1
58
+ Thread.stop
59
+ ordered << 2
60
+ f
61
+ end
62
+
63
+ thread_1 = Thread.new do
64
+ @frame_serializer.write_frame(mock('frame'))
65
+ end
66
+ thread_2 = Thread.new do
67
+ Thread.pass until call_next
68
+ Thread.pass
69
+ thread_1.run
70
+ end
71
+ Thread.pass until first_called
72
+ call_next = true
73
+
74
+ @frame_serializer.stub!(:__write_frame__).and_return do |f|
75
+ ordered << 3
76
+ f
77
+ end
78
+ @frame_serializer.write_frame(mock('frame'))
79
+ thread_1.join
80
+ thread_2.join
81
+ ordered.should == [1, 2, 3]
82
+ end
83
+
84
+ it "should synchronize reading from the underlying IO" do
85
+ first_called = false
86
+ call_next = false
87
+ ordered = []
88
+ @frame_serializer.stub!(:__read_frame__).and_return do
89
+ first_called = true
90
+ ordered << 1
91
+ Thread.stop
92
+ ordered << 2
93
+ mock('frame 1')
94
+ end
95
+
96
+ thread_1 = Thread.new do
97
+ @frame_serializer.read_frame
98
+ end
99
+ thread_2 = Thread.new do
100
+ Thread.pass until call_next
101
+ Thread.pass
102
+ thread_1.run
103
+ end
104
+ Thread.pass until first_called
105
+ call_next = true
106
+
107
+ @frame_serializer.stub!(:__read_frame__).and_return do
108
+ ordered << 3
109
+ mock('frame 2')
110
+ end
111
+ @frame_serializer.read_frame
112
+ thread_1.join
113
+ thread_2.join
114
+ ordered.should == [1, 2, 3]
115
+ end
116
+
117
+ it "should not make reading and writing mutually exclusive" do
118
+ first_called = false
119
+ call_next = false
120
+ ordered = []
121
+ @frame_serializer.stub!(:__write_frame__).and_return do |f|
122
+ first_called = true
123
+ ordered << 1
124
+ Thread.stop
125
+ ordered << 2
126
+ f
127
+ end
128
+ @frame_serializer.stub!(:__read_frame__).and_return do
129
+ ordered << 3
130
+ mock('frame 2')
131
+ end
132
+
133
+ thread_1 = Thread.new do
134
+ @frame_serializer.write_frame(mock('frame'))
135
+ end
136
+ thread_2 = Thread.new do
137
+ Thread.pass until call_next
138
+ Thread.pass
139
+ thread_1.run
140
+ end
141
+ Thread.pass until first_called
142
+ call_next = true
143
+ @frame_serializer.read_frame
144
+ thread_1.join
145
+ thread_2.join
146
+ ordered.should == [1, 3, 2]
147
+ end
148
+ end
149
+
150
+ describe "Protocol 1.0" do
151
+ it "should not have extended the V1_1 mixin" do
152
+ ::Stomper::FrameSerializer::EXTEND_BY_VERSION['1.1'].each do |mod|
153
+ @frame_serializer.should_not be_a_kind_of(mod)
154
+ end
155
+ end
156
+
157
+ describe "writing frames" do
158
+ it "should properly serialize a common frame" do
159
+ @frame_serializer.write_frame(@frames[:common])
160
+ @frame_io.string.should == "FRAME\nheader_1:value 1\nheader_2:3\nheader_3:\ncontent-type:text/plain;charset=UTF-8\ncontent-length:15\n\nbody of message\000"
161
+ end
162
+
163
+ it "should properly serialize a frame without headers - content type cannot be inferred in Ruby 1.8.7" do
164
+ @frame_serializer.write_frame(@frames[:no_headers])
165
+ @frame_io.string.should == "FRAME\ncontent-length:15\n\nbody of message\000"
166
+ end
167
+
168
+ it "should properly serialize a frame without a body" do
169
+ @frame_serializer.write_frame(@frames[:no_body])
170
+ @frame_io.string.should == "FRAME\nheader_1:val\nmusical:\noffering:4\n\n\000"
171
+ end
172
+
173
+ it "should properly serialize a frame without a command as a new line" do
174
+ @frame_serializer.write_frame(@frames[:no_command])
175
+ @frame_io.string.should == "\n"
176
+ end
177
+
178
+ it "should properly drop LF from header names and values" do
179
+ @frame_serializer.write_frame(@frames[:header_name_with_linefeed])
180
+ @frame_io.string.should == "FRAME\natestheader:va\\lue : isme\n\n\000"
181
+ end
182
+
183
+ it "should not escape backslash characters in header names or values" do
184
+ @frame_serializer.write_frame(@frames[:header_name_with_backslash])
185
+ @frame_io.string.should == "FRAME\na\\test\\header:va\\lue : isme\n\n\000"
186
+ end
187
+
188
+ it "should drop colons in header names, but leave them alone in values" do
189
+ @frame_serializer.write_frame(@frames[:header_name_with_colon])
190
+ @frame_io.string.should == "FRAME\natestheader:va\\lue : isme\n\n\000"
191
+ end
192
+
193
+ it "should not generate a content-type header if the encoding is binary" do
194
+ @frame_serializer.write_frame(@frames[:binary_body_no_content_type])
195
+ @frame_io.string.should == "FRAME\ncontent-length:15\n\nbody of message\000"
196
+ end
197
+
198
+ it "should preserve the charset parameter of text bodies, because we have nothing else to work with in Ruby 1.8.7" do
199
+ @frame_serializer.write_frame(@frames[:charset_header_text_body])
200
+ @frame_io.string.should == "FRAME\ncontent-type:text/plain; param=\"value\";charset=ISO-8859-1\ncontent-length:15\n\nbody of message\000"
201
+ end
202
+
203
+ it "should preserve the charset parameter of binary bodies, because we have nothing else to work with in Ruby 1.8.7" do
204
+ @frame_serializer.write_frame(@frames[:charset_header_binary_body])
205
+ @frame_io.string.should == "FRAME\ncontent-type:application/pdf; param=\"value\";charset=ISO-8859-1\ncontent-length:15\n\nbody of message\000"
206
+ end
207
+ end
208
+ end
209
+
210
+ describe "Protocol 1.1" do
211
+ before(:each) do
212
+ @frame_serializer.extend_for_protocol '1.1'
213
+ end
214
+
215
+ it "should have extended the V1_1 mixin" do
216
+ ::Stomper::FrameSerializer::EXTEND_BY_VERSION['1.1'].each do |mod|
217
+ @frame_serializer.should be_a_kind_of(mod)
218
+ end
219
+ end
220
+
221
+ describe "writing frames" do
222
+ it "should properly serialize a common frame" do
223
+ @frame_serializer.write_frame(@frames[:common])
224
+ @frame_io.string.should == "FRAME\nheader_1:value 1\nheader_2:3\nheader_3:\ncontent-type:text/plain;charset=UTF-8\ncontent-length:15\n\nbody of message\000"
225
+ end
226
+
227
+ it "should properly serialize a frame without headers - content type cannot be inferred in Ruby 1.8.7" do
228
+ @frame_serializer.write_frame(@frames[:no_headers])
229
+ @frame_io.string.should == "FRAME\ncontent-length:15\n\nbody of message\000"
230
+ end
231
+
232
+ it "should properly serialize a frame without a body" do
233
+ @frame_serializer.write_frame(@frames[:no_body])
234
+ @frame_io.string.should == "FRAME\nheader_1:val\nmusical:\noffering:4\n\n\000"
235
+ end
236
+
237
+ it "should properly serialize a frame without a command as a new line" do
238
+ @frame_serializer.write_frame(@frames[:no_command])
239
+ @frame_io.string.should == "\n"
240
+ end
241
+
242
+ it "should escape LF in header names and values" do
243
+ @frame_serializer.write_frame(@frames[:header_name_with_linefeed])
244
+ @frame_io.string.should == "FRAME\na\\ntest\\nheader:va\\\\lue \\c is\\n\\nme\n\n\000"
245
+ end
246
+
247
+ it "should escape backslashes in header names and values" do
248
+ @frame_serializer.write_frame(@frames[:header_name_with_backslash])
249
+ @frame_io.string.should == "FRAME\na\\\\test\\\\header:va\\\\lue \\c is\\n\\nme\n\n\000"
250
+ end
251
+
252
+ it "should escape colons in header names and values" do
253
+ @frame_serializer.write_frame(@frames[:header_name_with_colon])
254
+ @frame_io.string.should == "FRAME\na\\ctest\\cheader:va\\\\lue \\c is\\n\\nme\n\n\000"
255
+ end
256
+ end
257
+
258
+ describe "reading frames" do
259
+ it "should properly de-serialize a simple frame" do
260
+ @frame_io.string = @messages[:content_type_and_charset]
261
+ frame = @frame_serializer.read_frame
262
+ frame.command.should == "MESSAGE"
263
+ frame.headers.sort { |a, b| a.first <=> b.first }.should == [
264
+ ['a-header', ' padded '], ['content-length', '6'],
265
+ ['content-type', 'text/plain; charset=ISO-8859-1']
266
+ ]
267
+ frame.body.should == "h\xEBllo!"
268
+ end
269
+ it "should properly read a frame with special characters in its header" do
270
+ @frame_io.string = @messages[:escaped_headers]
271
+ frame = @frame_serializer.read_frame
272
+ frame["a\nspecial:head\\cer"].should == " padded: and using\nspecial\\\\\\ncharacters "
273
+ end
274
+ it "should properly read a frame with a body and no content-length" do
275
+ @frame_io.string = @messages[:no_content_length]
276
+ frame = @frame_serializer.read_frame
277
+ frame.body.should == "hëllo!"
278
+ end
279
+ it "should assume a binary charset if none is set and the content-type does not match text/*" do
280
+ @frame_io.string = @messages[:non_text_content_type]
281
+ frame = @frame_serializer.read_frame
282
+ end
283
+ it "should assume a binary charset if the content-type header is not specified" do
284
+ @frame_io.string = @messages[:no_content_type]
285
+ frame = @frame_serializer.read_frame
286
+ end
287
+ it "should set the value of a header to the first occurrence" do
288
+ @frame_io.string = @messages[:repeated_headers]
289
+ frame = @frame_serializer.read_frame
290
+ end
291
+ it "should raise a malformed frame error if the frame is not properly terminated" do
292
+ @frame_io.string = @messages[:invalid_content_length]
293
+ lambda { @frame_serializer.read_frame }.should raise_error(::Stomper::Errors::MalformedFrameError)
294
+ end
295
+ # While the spec suggests that all ":" chars be replaced with "\c", ActiveMQ 5.3.2 sends
296
+ # a "session" header with a value that contains ":" chars. So, we are NOT going to
297
+ # freak out if we receive more than one ":" on a header line.
298
+ it "should not raise an error if the frame contains a header value with a raw ':'" do
299
+ @frame_io.string = @messages[:invalid_header_character]
300
+ lambda { @frame_serializer.read_frame }.should_not raise_error
301
+ end
302
+ it "should raise an invalid header esacape sequence error if the frame contains a header with an invalid escape sequence" do
303
+ @frame_io.string = @messages[:invalid_header_sequence]
304
+ lambda { @frame_serializer.read_frame }.should raise_error(::Stomper::Errors::InvalidHeaderEscapeSequenceError)
305
+ end
306
+ it "should raise an malfored header error if the frame contains an incomplete header" do
307
+ @frame_io.string = @messages[:malformed_header]
308
+ lambda { @frame_serializer.read_frame }.should raise_error(::Stomper::Errors::MalformedHeaderError)
309
+ end
310
+ it "should raise an invalid header esacape sequence error if the frame contains a header with a dangling escape sequence" do
311
+ @frame_io.string = @messages[:dangling_header_sequence]
312
+ lambda { @frame_serializer.read_frame }.should raise_error(::Stomper::Errors::InvalidHeaderEscapeSequenceError)
313
+ end
314
+ end
315
+ end
316
+ end
317
+ end
318
+ end