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.
- data/.gitignore +5 -0
- data/{spec/spec.opts → .rspec} +0 -2
- data/Gemfile +4 -0
- data/LICENSE +201 -201
- data/README.md +130 -0
- data/Rakefile +5 -0
- data/examples/basic.rb +38 -0
- data/examples/events.rb +54 -0
- data/features/acking_messages.feature +147 -0
- data/features/disconnecting.feature +12 -0
- data/features/establish_connection.feature +44 -0
- data/features/protocol_version_negotiation.feature +61 -0
- data/features/receipts.feature +72 -0
- data/features/scopes.feature +32 -0
- data/features/secure_connections.feature +38 -0
- data/features/send_and_message.feature +28 -0
- data/features/steps/acking_messages_steps.rb +39 -0
- data/features/steps/disconnecting_steps.rb +8 -0
- data/features/steps/establish_connection_steps.rb +74 -0
- data/features/steps/frame_transmission_steps.rb +35 -0
- data/features/steps/protocol_version_negotiation_steps.rb +15 -0
- data/features/steps/receipts_steps.rb +79 -0
- data/features/steps/scopes_steps.rb +52 -0
- data/features/steps/secure_connections_steps.rb +41 -0
- data/features/steps/send_and_message_steps.rb +35 -0
- data/features/steps/subscribing_steps.rb +36 -0
- data/features/steps/threaded_receiver_steps.rb +8 -0
- data/features/steps/transactions_steps.rb +0 -0
- data/features/subscribing.feature +151 -0
- data/features/support/env.rb +11 -0
- data/features/support/header_helpers.rb +12 -0
- data/features/support/ssl/README +6 -0
- data/features/support/ssl/broker_cert.csr +17 -0
- data/features/support/ssl/broker_cert.pem +72 -0
- data/features/support/ssl/broker_key.pem +27 -0
- data/features/support/ssl/client_cert.csr +17 -0
- data/features/support/ssl/client_cert.pem +72 -0
- data/features/support/ssl/client_key.pem +27 -0
- data/features/support/ssl/demoCA/cacert.pem +17 -0
- data/features/support/ssl/demoCA/index.txt +2 -0
- data/features/support/ssl/demoCA/index.txt.attr +1 -0
- data/features/support/ssl/demoCA/index.txt.attr.old +1 -0
- data/features/support/ssl/demoCA/index.txt.old +1 -0
- data/features/support/ssl/demoCA/newcerts/01.pem +72 -0
- data/features/support/ssl/demoCA/newcerts/02.pem +72 -0
- data/features/support/ssl/demoCA/private/cakey.pem +17 -0
- data/features/support/ssl/demoCA/serial +1 -0
- data/features/support/ssl/demoCA/serial.old +1 -0
- data/features/support/test_stomp_server.rb +150 -0
- data/features/threaded_receiver.feature +11 -0
- data/features/transactions.feature +66 -0
- data/lib/stomper.rb +30 -20
- data/lib/stomper/connection.rb +442 -102
- data/lib/stomper/errors.rb +59 -0
- data/lib/stomper/extensions.rb +10 -0
- data/lib/stomper/extensions/common.rb +258 -0
- data/lib/stomper/extensions/events.rb +213 -0
- data/lib/stomper/extensions/heartbeat.rb +101 -0
- data/lib/stomper/extensions/scoping.rb +56 -0
- data/lib/stomper/frame.rb +54 -0
- data/lib/stomper/frame_serializer.rb +217 -0
- data/lib/stomper/headers.rb +15 -0
- data/lib/stomper/receipt_manager.rb +36 -0
- data/lib/stomper/receivers.rb +7 -0
- data/lib/stomper/receivers/threaded.rb +71 -0
- data/lib/stomper/scopes.rb +9 -0
- data/lib/stomper/scopes/header_scope.rb +49 -0
- data/lib/stomper/scopes/receipt_scope.rb +44 -0
- data/lib/stomper/scopes/transaction_scope.rb +109 -0
- data/lib/stomper/sockets.rb +66 -28
- data/lib/stomper/subscription_manager.rb +79 -0
- data/lib/stomper/support.rb +68 -0
- data/lib/stomper/support/1.8/frame_serializer.rb +53 -0
- data/lib/stomper/support/1.8/headers.rb +183 -0
- data/lib/stomper/support/1.9/frame_serializer.rb +64 -0
- data/lib/stomper/support/1.9/headers.rb +172 -0
- data/lib/stomper/support/ruby.rb +13 -0
- data/lib/stomper/uris.rb +49 -0
- data/lib/stomper/version.rb +7 -0
- data/spec/spec_helper.rb +13 -9
- data/spec/stomper/connection_spec.rb +712 -0
- data/spec/stomper/extensions/common_spec.rb +187 -0
- data/spec/stomper/extensions/events_spec.rb +78 -0
- data/spec/stomper/extensions/heartbeat_spec.rb +103 -0
- data/spec/stomper/extensions/scoping_spec.rb +21 -0
- data/spec/stomper/frame_serializer_1.8_spec.rb +318 -0
- data/spec/stomper/frame_serializer_spec.rb +316 -0
- data/spec/stomper/frame_spec.rb +36 -0
- data/spec/stomper/headers_spec.rb +224 -0
- data/spec/stomper/receipt_manager_spec.rb +91 -0
- data/spec/stomper/receivers/threaded_spec.rb +116 -0
- data/spec/stomper/scopes/header_scope_spec.rb +42 -0
- data/spec/stomper/scopes/receipt_scope_spec.rb +51 -0
- data/spec/stomper/scopes/transaction_scope_spec.rb +183 -0
- data/spec/stomper/sockets_spec.rb +113 -0
- data/spec/stomper/subscription_manager_spec.rb +107 -0
- data/spec/stomper/support_spec.rb +69 -0
- data/spec/stomper/uris_spec.rb +54 -0
- data/spec/stomper_spec.rb +9 -0
- data/spec/support/custom_argument_matchers.rb +57 -0
- data/spec/support/existential_frame_matchers.rb +19 -0
- data/spec/support/frame_header_matchers.rb +10 -0
- data/stomper.gemspec +30 -0
- metadata +272 -97
- data/AUTHORS +0 -21
- data/CHANGELOG +0 -20
- data/README.rdoc +0 -120
- data/lib/stomper/client.rb +0 -34
- data/lib/stomper/frame_reader.rb +0 -73
- data/lib/stomper/frame_writer.rb +0 -21
- data/lib/stomper/frames.rb +0 -39
- data/lib/stomper/frames/abort.rb +0 -10
- data/lib/stomper/frames/ack.rb +0 -25
- data/lib/stomper/frames/begin.rb +0 -11
- data/lib/stomper/frames/client_frame.rb +0 -89
- data/lib/stomper/frames/commit.rb +0 -10
- data/lib/stomper/frames/connect.rb +0 -10
- data/lib/stomper/frames/connected.rb +0 -30
- data/lib/stomper/frames/disconnect.rb +0 -10
- data/lib/stomper/frames/error.rb +0 -21
- data/lib/stomper/frames/message.rb +0 -48
- data/lib/stomper/frames/receipt.rb +0 -19
- data/lib/stomper/frames/send.rb +0 -10
- data/lib/stomper/frames/server_frame.rb +0 -38
- data/lib/stomper/frames/subscribe.rb +0 -42
- data/lib/stomper/frames/unsubscribe.rb +0 -19
- data/lib/stomper/open_uri_interface.rb +0 -41
- data/lib/stomper/receipt_handlers.rb +0 -23
- data/lib/stomper/receiptor.rb +0 -38
- data/lib/stomper/subscriber.rb +0 -76
- data/lib/stomper/subscription.rb +0 -128
- data/lib/stomper/subscriptions.rb +0 -95
- data/lib/stomper/threaded_receiver.rb +0 -59
- data/lib/stomper/transaction.rb +0 -185
- data/lib/stomper/transactor.rb +0 -50
- data/lib/stomper/uri.rb +0 -55
- data/spec/client_spec.rb +0 -29
- data/spec/connection_spec.rb +0 -22
- data/spec/frame_reader_spec.rb +0 -37
- data/spec/frame_writer_spec.rb +0 -27
- data/spec/frames/client_frame_spec.rb +0 -66
- data/spec/frames/indirect_frame_spec.rb +0 -45
- data/spec/frames/server_frame_spec.rb +0 -85
- data/spec/open_uri_interface_spec.rb +0 -132
- data/spec/receiptor_spec.rb +0 -35
- data/spec/shared_connection_examples.rb +0 -79
- data/spec/subscriber_spec.rb +0 -77
- data/spec/subscription_spec.rb +0 -157
- data/spec/subscriptions_spec.rb +0 -145
- data/spec/threaded_receiver_spec.rb +0 -33
- data/spec/transaction_spec.rb +0 -139
- 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
|