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