stomper 1.0.0 → 2.0.0

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