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,316 @@
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
+ @messages.each { |k, v| v.force_encoding('ASCII-8BIT') }
22
+
23
+ @frames = {
24
+ :common => ::Stomper::Frame.new('FRAME', { :header_1 => 'value 1', :header_2 => '3', :header_3 => '', :'content-type' => 'text/plain'}, 'body of message'),
25
+ :no_headers => ::Stomper::Frame.new('FRAME', {}, 'body of message'.encode('ISO-8859-1')),
26
+ :no_body => ::Stomper::Frame.new('FRAME', { :header_1 => 'val', :musical => '', :offering => '4'}),
27
+ :no_command => ::Stomper::Frame.new,
28
+ :header_name_with_linefeed => ::Stomper::Frame.new('FRAME', { "a\ntest\nheader" => "va\\lue : is\n\nme"}),
29
+ :header_name_with_colon => ::Stomper::Frame.new('FRAME', { "a:test:header" => "va\\lue : is\n\nme"}),
30
+ :header_name_with_backslash => ::Stomper::Frame.new('FRAME', { "a\\test\\header" => "va\\lue : is\n\nme"}),
31
+ :binary_body_no_content_type => ::Stomper::Frame.new('FRAME', {}, 'body of message'.encode('ASCII-8BIT')),
32
+ :charset_header_text_body => ::Stomper::Frame.new('FRAME', {:'content-type' => 'text/plain; param="value";charset=ISO-8859-1'}, 'body of message'.encode('UTF-8')),
33
+ :charset_header_binary_body => ::Stomper::Frame.new('FRAME', {:'content-type' => 'application/pdf; param="value";charset=ISO-8859-1'}, 'body of message'.encode('ASCII-8BIT'))
34
+ }
35
+ @frame_io = StringIO.new
36
+ @frame_serializer = FrameSerializer.new(@frame_io)
37
+ end
38
+
39
+ describe "thread safety" do
40
+ before(:each) do
41
+ @frame_serializer = FrameSerializer.new(mock('frame io'))
42
+ end
43
+ it "should synchronize writing to the underlying IO" do
44
+ first_called = false
45
+ call_next = false
46
+ ordered = []
47
+ @frame_serializer.stub!(:__write_frame__).and_return do |f|
48
+ first_called = true
49
+ ordered << 1
50
+ Thread.stop
51
+ ordered << 2
52
+ f
53
+ end
54
+
55
+ thread_1 = Thread.new do
56
+ @frame_serializer.write_frame(mock('frame'))
57
+ end
58
+ thread_2 = Thread.new do
59
+ Thread.pass until call_next
60
+ Thread.pass
61
+ thread_1.run
62
+ end
63
+ Thread.pass until first_called
64
+ call_next = true
65
+
66
+ @frame_serializer.stub!(:__write_frame__).and_return do |f|
67
+ ordered << 3
68
+ f
69
+ end
70
+ @frame_serializer.write_frame(mock('frame'))
71
+ thread_1.join
72
+ thread_2.join
73
+ ordered.should == [1, 2, 3]
74
+ end
75
+
76
+ it "should synchronize reading from the underlying IO" do
77
+ first_called = false
78
+ call_next = false
79
+ ordered = []
80
+ @frame_serializer.stub!(:__read_frame__).and_return do
81
+ first_called = true
82
+ ordered << 1
83
+ Thread.stop
84
+ ordered << 2
85
+ mock('frame 1')
86
+ end
87
+
88
+ thread_1 = Thread.new do
89
+ @frame_serializer.read_frame
90
+ end
91
+ thread_2 = Thread.new do
92
+ Thread.pass until call_next
93
+ Thread.pass
94
+ thread_1.run
95
+ end
96
+ Thread.pass until first_called
97
+ call_next = true
98
+
99
+ @frame_serializer.stub!(:__read_frame__).and_return do
100
+ ordered << 3
101
+ mock('frame 2')
102
+ end
103
+ @frame_serializer.read_frame
104
+ thread_1.join
105
+ thread_2.join
106
+ ordered.should == [1, 2, 3]
107
+ end
108
+
109
+ it "should not make reading and writing mutually exclusive" do
110
+ first_called = false
111
+ call_next = false
112
+ ordered = []
113
+ @frame_serializer.stub!(:__write_frame__).and_return do |f|
114
+ first_called = true
115
+ ordered << 1
116
+ Thread.stop
117
+ ordered << 2
118
+ f
119
+ end
120
+ @frame_serializer.stub!(:__read_frame__).and_return do
121
+ ordered << 3
122
+ mock('frame 2')
123
+ end
124
+
125
+ thread_1 = Thread.new do
126
+ @frame_serializer.write_frame(mock('frame'))
127
+ end
128
+ thread_2 = Thread.new do
129
+ Thread.pass until call_next
130
+ Thread.pass
131
+ thread_1.run
132
+ end
133
+ Thread.pass until first_called
134
+ call_next = true
135
+ @frame_serializer.read_frame
136
+ thread_1.join
137
+ thread_2.join
138
+ ordered.should == [1, 3, 2]
139
+ end
140
+ end
141
+
142
+ describe "Protocol 1.0" do
143
+ it "should not have extended the V1_1 mixin" do
144
+ ::Stomper::FrameSerializer::EXTEND_BY_VERSION['1.1'].each do |mod|
145
+ @frame_serializer.should_not be_a_kind_of(mod)
146
+ end
147
+ end
148
+
149
+ describe "writing frames" do
150
+ it "should properly serialize a common frame" do
151
+ @frame_serializer.write_frame(@frames[:common])
152
+ @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"
153
+ end
154
+
155
+ it "should properly serialize a frame without headers" do
156
+ @frame_serializer.write_frame(@frames[:no_headers])
157
+ @frame_io.string.should == "FRAME\ncontent-type:text/plain;charset=ISO-8859-1\ncontent-length:15\n\nbody of message\000"
158
+ end
159
+
160
+ it "should properly serialize a frame without a body" do
161
+ @frame_serializer.write_frame(@frames[:no_body])
162
+ @frame_io.string.should == "FRAME\nheader_1:val\nmusical:\noffering:4\n\n\000"
163
+ end
164
+
165
+ it "should properly serialize a frame without a command as a new line" do
166
+ @frame_serializer.write_frame(@frames[:no_command])
167
+ @frame_io.string.should == "\n"
168
+ end
169
+
170
+ it "should properly drop LF from header names and values" do
171
+ @frame_serializer.write_frame(@frames[:header_name_with_linefeed])
172
+ @frame_io.string.should == "FRAME\natestheader:va\\lue : isme\n\n\000"
173
+ end
174
+
175
+ it "should not escape backslash characters in header names or values" do
176
+ @frame_serializer.write_frame(@frames[:header_name_with_backslash])
177
+ @frame_io.string.should == "FRAME\na\\test\\header:va\\lue : isme\n\n\000"
178
+ end
179
+
180
+ it "should drop colons in header names, but leave them alone in values" do
181
+ @frame_serializer.write_frame(@frames[:header_name_with_colon])
182
+ @frame_io.string.should == "FRAME\natestheader:va\\lue : isme\n\n\000"
183
+ end
184
+
185
+ it "should not generate a content-type header if the encoding is binary" do
186
+ @frame_serializer.write_frame(@frames[:binary_body_no_content_type])
187
+ @frame_io.string.should == "FRAME\ncontent-length:15\n\nbody of message\000"
188
+ end
189
+
190
+ it "should overwrite the charset parameter with the encoding when the body is text" do
191
+ @frame_serializer.write_frame(@frames[:charset_header_text_body])
192
+ @frame_io.string.should == "FRAME\ncontent-type:text/plain; param=\"value\";charset=UTF-8\ncontent-length:15\n\nbody of message\000"
193
+ end
194
+
195
+ it "should omit the charset parameter when the content-type is not text/* and the encoding is binary" do
196
+ @frame_serializer.write_frame(@frames[:charset_header_binary_body])
197
+ @frame_io.string.should == "FRAME\ncontent-type:application/pdf; param=\"value\"\ncontent-length:15\n\nbody of message\000"
198
+ end
199
+ end
200
+ end
201
+
202
+ describe "Protocol 1.1" do
203
+ before(:each) do
204
+ @frame_serializer.extend_for_protocol '1.1'
205
+ end
206
+
207
+ it "should have extended the V1_1 mixin" do
208
+ ::Stomper::FrameSerializer::EXTEND_BY_VERSION['1.1'].each do |mod|
209
+ @frame_serializer.should be_a_kind_of(mod)
210
+ end
211
+ end
212
+
213
+ describe "writing frames" do
214
+ it "should properly serialize a common frame" do
215
+ @frame_serializer.write_frame(@frames[:common])
216
+ @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"
217
+ end
218
+
219
+ it "should properly serialize a frame without headers" do
220
+ @frame_serializer.write_frame(@frames[:no_headers])
221
+ @frame_io.string.should == "FRAME\ncontent-type:text/plain;charset=ISO-8859-1\ncontent-length:15\n\nbody of message\000"
222
+ end
223
+
224
+ it "should properly serialize a frame without a body" do
225
+ @frame_serializer.write_frame(@frames[:no_body])
226
+ @frame_io.string.should == "FRAME\nheader_1:val\nmusical:\noffering:4\n\n\000"
227
+ end
228
+
229
+ it "should properly serialize a frame without a command as a new line" do
230
+ @frame_serializer.write_frame(@frames[:no_command])
231
+ @frame_io.string.should == "\n"
232
+ end
233
+
234
+ it "should escape LF in header names and values" do
235
+ @frame_serializer.write_frame(@frames[:header_name_with_linefeed])
236
+ @frame_io.string.should == "FRAME\na\\ntest\\nheader:va\\\\lue \\c is\\n\\nme\n\n\000"
237
+ end
238
+
239
+ it "should escape backslashes in header names and values" do
240
+ @frame_serializer.write_frame(@frames[:header_name_with_backslash])
241
+ @frame_io.string.should == "FRAME\na\\\\test\\\\header:va\\\\lue \\c is\\n\\nme\n\n\000"
242
+ end
243
+
244
+ it "should escape colons in header names and values" do
245
+ @frame_serializer.write_frame(@frames[:header_name_with_colon])
246
+ @frame_io.string.should == "FRAME\na\\ctest\\cheader:va\\\\lue \\c is\\n\\nme\n\n\000"
247
+ end
248
+ end
249
+
250
+ describe "reading frames" do
251
+ it "should properly de-serialize a simple frame" do
252
+ @frame_io.string = @messages[:content_type_and_charset]
253
+ frame = @frame_serializer.read_frame
254
+ frame.command.should == "MESSAGE"
255
+ frame.headers.sort { |a, b| a.first <=> b.first }.should == [
256
+ ['a-header', ' padded '], ['content-length', '6'],
257
+ ['content-type', 'text/plain; charset=ISO-8859-1']
258
+ ]
259
+ frame.body.should == "hëllo!".encode("ISO-8859-1")
260
+ frame.body.encoding.name.should == 'ISO-8859-1'
261
+ end
262
+ it "should properly read a frame with special characters in its header" do
263
+ @frame_io.string = @messages[:escaped_headers]
264
+ frame = @frame_serializer.read_frame
265
+ frame["a\nspecial:head\\cer"].should == " padded: and using\nspecial\\\\\\ncharacters "
266
+ frame.body.encoding.name.should == 'UTF-8'
267
+ end
268
+ it "should properly read a frame with a body and no content-length" do
269
+ @frame_io.string = @messages[:no_content_length]
270
+ frame = @frame_serializer.read_frame
271
+ frame.body.should == "hëllo!"
272
+ frame.body.encoding.name.should == 'UTF-8'
273
+ end
274
+ it "should assume a binary charset if none is set and the content-type does not match text/*" do
275
+ @frame_io.string = @messages[:non_text_content_type]
276
+ frame = @frame_serializer.read_frame
277
+ frame.body.encoding.name.should == 'ASCII-8BIT'
278
+ end
279
+ it "should assume a binary charset if the content-type header is not specified" do
280
+ @frame_io.string = @messages[:no_content_type]
281
+ frame = @frame_serializer.read_frame
282
+ frame.body.encoding.name.should == 'ASCII-8BIT'
283
+ end
284
+ it "should set the value of a header to the first occurrence" do
285
+ @frame_io.string = @messages[:repeated_headers]
286
+ frame = @frame_serializer.read_frame
287
+ frame['repeated header'].should == 'a value'
288
+ end
289
+ it "should raise a malformed frame error if the frame is not properly terminated" do
290
+ @frame_io.string = @messages[:invalid_content_length]
291
+ lambda { @frame_serializer.read_frame }.should raise_error(::Stomper::Errors::MalformedFrameError)
292
+ end
293
+ # While the spec suggests that all ":" chars be replaced with "\c", ActiveMQ 5.3.2 sends
294
+ # a "session" header with a value that contains ":" chars. So, we are NOT going to
295
+ # freak out if we receive more than one ":" on a header line.
296
+ it "should not raise an error if the frame contains a header value with a raw ':'" do
297
+ @frame_io.string = @messages[:invalid_header_character]
298
+ lambda { @frame_serializer.read_frame }.should_not raise_error
299
+ end
300
+ it "should raise an invalid header esacape sequence error if the frame contains a header with an invalid escape sequence" do
301
+ @frame_io.string = @messages[:invalid_header_sequence]
302
+ lambda { @frame_serializer.read_frame }.should raise_error(::Stomper::Errors::InvalidHeaderEscapeSequenceError)
303
+ end
304
+ it "should raise an malfored header error if the frame contains an incomplete header" do
305
+ @frame_io.string = @messages[:malformed_header]
306
+ lambda { @frame_serializer.read_frame }.should raise_error(::Stomper::Errors::MalformedHeaderError)
307
+ end
308
+ it "should raise an invalid header esacape sequence error if the frame contains a header with a dangling escape sequence" do
309
+ @frame_io.string = @messages[:dangling_header_sequence]
310
+ lambda { @frame_serializer.read_frame }.should raise_error(::Stomper::Errors::InvalidHeaderEscapeSequenceError)
311
+ end
312
+ end
313
+ end
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ module Stomper
5
+ describe Frame do
6
+ before(:each) do
7
+ @frame = Frame.new
8
+ end
9
+
10
+ it "should have a command attribute" do
11
+ @frame.should respond_to(:command)
12
+ @frame.should respond_to(:command=)
13
+ @frame.command = 'command name'
14
+ @frame.command.should == 'command name'
15
+ end
16
+
17
+ describe "headers" do
18
+ it "should provide access to the headers through :headers" do
19
+ @frame.headers.should be_a_kind_of(::Stomper::Headers)
20
+ end
21
+
22
+ it "should provide hash-like access to header assignment" do
23
+ @frame['header name'] = 'header value'
24
+ @frame['header name'].should == 'header value'
25
+ end
26
+
27
+ it "should provide a convenience method for content-type" do
28
+ @frame[:'content-type'] = 'text/plain; charset=UTF-8; param=val'
29
+ @frame.content_type.should == 'text/plain'
30
+
31
+ @frame[:'content-type'] = nil
32
+ @frame.content_type.should == ''
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,224 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ module Stomper
5
+ describe Headers do
6
+ before(:each) do
7
+ @headers = Headers.new
8
+ end
9
+ after(:each) do
10
+ @headers.names.should == @headers.names.uniq
11
+ end
12
+
13
+ it "should allow setting and getting headers through a hash-style" do
14
+ @headers[:header_1] = 'some value'
15
+ @headers[:header_1].should == 'some value'
16
+ end
17
+
18
+ it "should translate keys to symbols" do
19
+ @headers['other header'] = '42'
20
+ @headers[:'other header'].should == '42'
21
+ @headers['other header'].should == '42'
22
+ end
23
+
24
+ it "should translate header values to strings" do
25
+ @headers['test header'] = 42
26
+ @headers['test header'].should == '42'
27
+ @headers['test header'] = nil
28
+ @headers['test header'].should == ''
29
+ end
30
+
31
+ it "should return an array of header key/value pairs" do
32
+ @headers['header 1'] = 'testing'
33
+ @headers['other header'] = 19
34
+ @headers[:tom] = 'servo'
35
+ @headers.to_a.should == [ ['header 1', 'testing'], ['other header', '19'], ['tom', 'servo'] ]
36
+ end
37
+
38
+ it "should preserve the order of keys" do
39
+ expected_keys = []
40
+ 20.times do |n|
41
+ expected_keys << :"header #{n}"
42
+ @headers["header #{n}"] = 'value'
43
+ end
44
+ @headers.names.should == expected_keys
45
+ end
46
+
47
+ it "should preserve the order of keys after deletion and insertion" do
48
+ expected_keys = []
49
+ 20.times do |n|
50
+ expected_keys << :"header #{n}" unless n == 3 || n == 16
51
+ @headers["header #{n}"] = 'value'
52
+ end
53
+ expected_keys << :'header x'
54
+ @headers.delete('header 3')
55
+ @headers.delete('header 16')
56
+ @headers['header x'] = 'value'
57
+ @headers.names.should == expected_keys
58
+ end
59
+
60
+ it "should not duplicate existing keys" do
61
+ expected_keys = []
62
+ 10.times do |n|
63
+ expected_keys << :"header #{n}"
64
+ @headers["header #{n}"] = 'value'
65
+ @headers["header #{n}"] = 'other value'
66
+ @headers.append("header #{n}", 'last value')
67
+ end
68
+ @headers.names.should == expected_keys
69
+ end
70
+
71
+ it "should maintain case sensitivity of keys" do
72
+ @headers['headeR'] = 'value 1'
73
+ @headers['Header'] = 'value 2'
74
+ @headers[:header] = 'value 3'
75
+ @headers['headeR'].should == 'value 1'
76
+ @headers['Header'].should == 'value 2'
77
+ @headers['header'].should == 'value 3'
78
+ end
79
+
80
+ it "should overwrite header values through the hash-like interface" do
81
+ @headers['header 1'] = 'first value'
82
+ @headers[:'header 1'] = 'second value'
83
+ @headers['header 1'] = 'third value'
84
+ @headers[:'header 1'].should == 'third value'
85
+ end
86
+
87
+ it "should allow appending headers through the append interface method" do
88
+ @headers.append('header 1', 'first value')
89
+ @headers.append(:'header 1', 'second value')
90
+ @headers.append('header 1', 'third value')
91
+ @headers.all_values('header 1').should == ['first value', 'second value', 'third value']
92
+ end
93
+
94
+ it "should be convertable to an array" do
95
+ @headers['header 1'] = 'first value'
96
+ @headers['header 2'] = 'second value'
97
+ @headers['header 3'] = 'third value'
98
+ @headers.to_a.should == [['header 1', 'first value'], ['header 2', 'second value'], ['header 3', 'third value']]
99
+ end
100
+
101
+ it "should include duplicate headers in the array conversion" do
102
+ @headers.append('header 1', 'h1 value 1')
103
+ @headers.append('header 1', 'h1 value 2')
104
+ @headers.append('header 1', 'h1 value 3')
105
+ @headers['header 2'] = 'h2 value 1'
106
+ @headers['header 3'] = 'h3 value 1'
107
+ @headers.append('header 3', 'h3 value 2')
108
+ @headers.to_a.should == [ ['header 1', 'h1 value 1'], ['header 1', 'h1 value 2'],
109
+ ['header 1', 'h1 value 3'], ['header 2', 'h2 value 1'], ['header 3', 'h3 value 1'],
110
+ ['header 3', 'h3 value 2'] ]
111
+ end
112
+
113
+ it "should set a header value to the first encountered in a chain of appends" do
114
+ @headers.append('header 1', 'first value')
115
+ @headers.append('header 1', 'second value')
116
+ @headers.append('header 1', 'third value')
117
+ @headers['header 1'].should == 'first value'
118
+ end
119
+
120
+ it "should be able to verify if a header has been set" do
121
+ @headers['header 1'] = 'testing'
122
+ @headers.has?(:'header 1').should be_true
123
+ @headers.key?('header 1').should be_true
124
+ @headers.include?('header 1').should be_true
125
+ @headers.delete(:'header 1')
126
+ @headers.has?(:'header 1').should_not be_true
127
+ @headers.key?('header 1').should_not be_true
128
+ @headers.include?('header 1').should_not be_true
129
+ end
130
+
131
+ describe "merging" do
132
+ before(:each) do
133
+ @headers = Headers.new({ :name1 => 'value 1', :name2 => 42, :name3 => false })
134
+ end
135
+
136
+ it "should have header names and values from the hash it was initialized from" do
137
+ @headers.names.sort.should == [ :name1, :name2, :name3 ]
138
+ @headers[:name1].should == 'value 1'
139
+ @headers[:name2].should == '42'
140
+ @headers[:name3].should == 'false'
141
+ end
142
+
143
+ it "should include keys and values from a merge, overwriting existing values" do
144
+ @headers.merge!({ 'name2' => 'frankenberry', :name1 => 'faust', :name4 => 186 })
145
+ @headers[:name1].should == 'faust'
146
+ @headers[:name2].should == 'frankenberry'
147
+ @headers[:name3].should == 'false'
148
+ @headers[:name4].should == '186'
149
+ end
150
+
151
+ it "should include keys and values from a reverse merge, only if it did not have those names" do
152
+ @headers.reverse_merge!(:name1 => 'chicken', :name5 => true, 'name2' => 1066)
153
+ @headers[:name1].should == 'value 1'
154
+ @headers[:name2].should == '42'
155
+ @headers[:name3].should == 'false'
156
+ @headers[:name5].should == 'true'
157
+ end
158
+ end
159
+
160
+ describe "enumerability" do
161
+ before(:each) do
162
+ @headers['header 1'] = 'value 1'
163
+ @headers.append('header 2', 'value 2')
164
+ @headers.append('header 2', 'value 3')
165
+ @expected_names = ['header 1', 'header 2', 'header 2']
166
+ @expected_values = ['value 1', 'value 2', 'value 3']
167
+ @received_names = []
168
+ @received_values = []
169
+ @iteration_result = nil
170
+ end
171
+
172
+ def iterate_with_arity_one(meth, collection=@headers)
173
+ @received_names.clear
174
+ @received_values.clear
175
+ @iteration_result = collection.__send__(meth) do |kvp|
176
+ @received_names << kvp.first
177
+ @received_values << kvp.last
178
+ end
179
+ end
180
+
181
+ def iterate_with_arity_two(meth, collection=@headers)
182
+ @received_names.clear
183
+ @received_values.clear
184
+ @iteration_result = collection.__send__(meth) do |k,v|
185
+ @received_names << k
186
+ @received_values << v
187
+ end
188
+ end
189
+
190
+ it "should be an Enumerable" do
191
+ @headers.should be_a_kind_of(::Enumerable)
192
+ end
193
+
194
+ it "should iterate with :each, yielding appropriately depending on the arity of the block" do
195
+ iterate_with_arity_one(:each)
196
+ @received_names.should == @expected_names
197
+ @received_values.should == @expected_values
198
+ @iteration_result.should equal(@headers)
199
+
200
+ iterate_with_arity_two(:each)
201
+ @received_names.should == @expected_names
202
+ @received_values.should == @expected_values
203
+ @iteration_result.should equal(@headers)
204
+ end
205
+
206
+ it "should yield an Enumerator if :each is called without a block" do
207
+ enum = @headers.each
208
+ if RUBY_VERSION >= '1.9'
209
+ enum.should be_a_kind_of(::Enumerator)
210
+ else
211
+ enum.should be_a_kind_of(::Enumerable::Enumerator)
212
+ end
213
+
214
+ iterate_with_arity_one(:each, enum)
215
+ @received_names.should == @expected_names
216
+ @received_values.should == @expected_values
217
+
218
+ iterate_with_arity_two(:each, enum)
219
+ @received_names.should == @expected_names
220
+ @received_values.should == @expected_values
221
+ end
222
+ end
223
+ end
224
+ end